Compare commits
41
Commits
f816bf005e
...
deploy
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74934d4559 | ||
|
|
027f88da6c | ||
|
|
6dc2965209 | ||
|
|
c69c1aa855 | ||
|
|
9f0e03c151 | ||
|
|
471a431af5 | ||
|
|
26d8410649 | ||
|
|
8403ad556c | ||
|
|
d26c15f857 | ||
|
|
86ed664e3e | ||
|
|
a105f395dd | ||
|
|
a5ebc12580 | ||
|
|
270f6e366f | ||
|
|
acf215c1bf | ||
|
|
8a98cd7d19 | ||
|
|
3a5b48b608 | ||
|
|
d735ad7dd7 | ||
|
|
25dd0f1504 | ||
|
|
8e14c6cef5 | ||
|
|
9f7abe4413 | ||
|
|
de730205af | ||
|
|
a873f0e0dc | ||
|
|
32e3765b52 | ||
|
|
4f62095e8e | ||
|
|
5234448ae5 | ||
|
|
f284c67988 | ||
|
|
2d77813dab | ||
|
|
d22a255143 | ||
|
|
6703bbeeec | ||
|
|
d444c591ec | ||
|
|
f7ddb6cae0 | ||
|
|
7ffc02e887 | ||
|
|
cbacb27bb1 | ||
|
|
03a08a320a | ||
|
|
45223be64a | ||
|
|
b22dfdfa4c | ||
|
|
ce219f0e40 | ||
|
|
a0f26d38b2 | ||
|
|
4efdcfe0fc | ||
|
|
806ccf950f | ||
|
|
b96b78fa0b |
@@ -33,10 +33,10 @@
|
||||
├──────────────────────────────────────────────────────────────────────────────────────────────────────┤
|
||||
│ [영역 B] 필수첨부파일 목록 │
|
||||
│ ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ 필수첨부파일아이디 | 문서구분 | 필수첨부파일명 | 필수첨부여부 | 비고 | 등록일시 | 수정일시 | 관리 │
|
||||
│ │ 필수첨부파일아이디 | 문서구분 | 필수첨부파일명 | 필수첨부여부 | 필수파일검토내용 | 비고 | 관리 │ │
|
||||
│ ├────────────────────────────────────────────────────────────────────────────────────────────────┤ │
|
||||
│ │ EAF-0001 | 설치인가 | 사업계획서 | 예 | 서명본 첨부 | 2026-04-22 10:10:00 | — | [수정][삭제] │ │
|
||||
│ │ EAF-0002 | 기본계획 | 변경사유서 | 예 | — | 2026-04-22 11:00:00 | — | [수정][삭제] │ │
|
||||
│ │ EAF-0001 | 설치인가 | 사업계획서 | 예 | 사업비 내역 검토 | 서명본 첨부 | [수정][삭제] │ │
|
||||
│ │ EAF-0002 | 기본계획 | 변경사유서 | 예 | 변경 전후 비교표 | — | [수정][삭제] │ │
|
||||
│ └────────────────────────────────────────────────────────────────────────────────────────────────┘ │
|
||||
└──────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
@@ -47,7 +47,9 @@
|
||||
│ 필수첨부파일아이디(자동발급) [EAF-0004] │
|
||||
│ 문서구분 [설치인가 ▼] │
|
||||
│ 필수첨부파일명 [예: 사업계획서] │
|
||||
│ 양식파일 [파일 선택] │
|
||||
│ 필수첨부여부 [ ] 예 [ ] 아니오 │
|
||||
│ 필수파일검토내용 [검토내용을 입력하세요]│
|
||||
│ 비고 [비고를 입력하세요] │
|
||||
├──────────────────────────────────────────────────────┤
|
||||
│ [취소] [저장] │
|
||||
@@ -139,16 +141,16 @@ END
|
||||
|
||||
### 5-2. 그리드 컬럼
|
||||
|
||||
| 컬럼명 | Field | 타입 | 비고 |
|
||||
| ------------------ | ------------------- | ------ | ------------------------------------- |
|
||||
| 필수첨부파일아이디 | `esntlAtchFileId` | string | 예: `EAF-0001`, 클라이언트 생성/입력 |
|
||||
| 문서구분 | `docSeComCd` | string | 코드명 표시 — 공통코드 (TBD 상위코드) |
|
||||
| 필수첨부파일명 | `esntlAtchFileNm` | string | |
|
||||
| 필수첨부여부 | `esntlAtchYn` | string | `Y`→예, `N`→아니오 |
|
||||
| 비고 | `esntlAtchFileRmrk` | string | 없으면 `—` 표시 |
|
||||
| 등록일시 | `regDt` | string | `yyyy-MM-dd HH:mm:ss` |
|
||||
| 수정일시 | `mdfcnDt` | string | 미수정 시 빈 값 또는 `—` |
|
||||
| 관리 | — | — | 행별 **[수정] · [삭제]** |
|
||||
| 컬럼명 | Field | 타입 | 비고 |
|
||||
| ------------------ | -------------------- | ------ | ------------------------------------- |
|
||||
| 필수첨부파일아이디 | `esntlAtchFileId` | string | 예: `EAF-0001`, 클라이언트 생성/입력 |
|
||||
| 문서구분 | `docSeComCd` | string | 코드명 표시 — 공통코드 (TBD 상위코드) |
|
||||
| 필수첨부파일명 | `esntlAtchFileNm` | string | |
|
||||
| 필수첨부여부 | `esntlAtchYn` | string | `Y`→예, `N`→아니오 |
|
||||
| 필수파일검토내용 | `esntlAtchFileRvwCn` | string | 없으면 `—` 표시, 최대 2줄 clamp |
|
||||
| 비고 | `esntlAtchFileRmrk` | string | 없으면 `—` 표시 |
|
||||
| 양식 내려받기 | `atchFileGroupId` | string | 양식파일 다운로드 버튼 |
|
||||
| 관리 | — | — | 행별 **[수정] · [삭제]** |
|
||||
|
||||
- 행 클릭 시 별도 상세 화면 없음. 편집은 **[수정]** 버튼으로 다이얼로그 C를 연다.
|
||||
|
||||
@@ -167,13 +169,15 @@ END
|
||||
|
||||
### 6-1. 폼 필드
|
||||
|
||||
| 표시명 | 입력 유형 | 필수 | 기본값 | API 파라미터 매핑 | 비고 |
|
||||
| ------------------ | ---------- | ---- | ------ | ------------------- | -------------------------------- |
|
||||
| 필수첨부파일아이디 | (Readonly) | Y | — | `esntlAtchFileId` | 등록 시 미노출 |
|
||||
| 문서구분 | CodeSelect | Y | — | `docSeComCd` | 예: 설치인가, 기본계획 |
|
||||
| 필수첨부파일명 | Text | Y | — | `esntlAtchFileNm` | placeholder: "예: 사업계획서" |
|
||||
| 필수첨부여부 | RadioGroup | Y | Y | `esntlAtchYn` | Y/N, UI 표시: 예/아니오 |
|
||||
| 비고 | Textarea | N | — | `esntlAtchFileRmrk` | placeholder: "비고를 입력하세요" |
|
||||
| 표시명 | 입력 유형 | 필수 | 기본값 | API 파라미터 매핑 | 비고 |
|
||||
| ------------------ | ------------------- | ---- | ------ | -------------------- | ---------------------------------- |
|
||||
| 필수첨부파일아이디 | (Readonly) | Y | — | `esntlAtchFileId` | 등록 시 미노출 |
|
||||
| 문서구분 | CodeSelect | Y | — | `docSeComCd` | 예: 설치인가, 기본계획 |
|
||||
| 필수첨부파일명 | Text | Y | — | `esntlAtchFileNm` | placeholder: "예: 사업계획서" |
|
||||
| 양식파일 | AttachmentFileField | N | — | `atchFileGroupId` | TUS 파일 업로드 |
|
||||
| 필수첨부여부 | RadioGroup | Y | Y | `esntlAtchYn` | Y/N, UI 표시: 예/아니오 |
|
||||
| 필수파일검토내용 | Textarea | N | — | `esntlAtchFileRvwCn` | placeholder: "검토내용을 입력하세요"|
|
||||
| 비고 | Textarea | N | — | `esntlAtchFileRmrk` | placeholder: "비고를 입력하세요" |
|
||||
|
||||
### 6-2. 버튼
|
||||
|
||||
@@ -210,28 +214,32 @@ END
|
||||
|
||||
**응답 DTO**
|
||||
|
||||
| Field | 타입 | 설명 |
|
||||
| ------------------- | ------- | -------------------------------- |
|
||||
| `esntlAtchFileId` | string | 필수첨부파일아이디 |
|
||||
| `docSeComCd` | string | 문서구분공통코드 |
|
||||
| `esntlAtchFileNm` | string | 필수첨부파일명 |
|
||||
| `esntlAtchYn` | string | 필수첨부여부 (Y/N) |
|
||||
| `esntlAtchFileRmrk` | string? | 필수첨부파일비고 |
|
||||
| `rgtr` | string? | 등록자 UUID |
|
||||
| `rgtrNm` | string? | 등록자명 |
|
||||
| `mdfr` | string? | 수정자 UUID |
|
||||
| `regDt` | string? | 등록일시 (`yyyy-MM-dd HH:mm:ss`) |
|
||||
| `mdfcnDt` | string? | 수정일시 |
|
||||
| Field | 타입 | 설명 |
|
||||
| -------------------- | -------------------------- | -------------------------------- |
|
||||
| `esntlAtchFileId` | string | 필수첨부파일아이디 |
|
||||
| `docSeComCd` | string | 문서구분공통코드 |
|
||||
| `esntlAtchFileNm` | string | 필수첨부파일명 |
|
||||
| `esntlAtchYn` | string | 필수첨부여부 (Y/N) |
|
||||
| `esntlAtchFileRvwCn` | string? | 필수첨부파일검토내용 |
|
||||
| `esntlAtchFileRmrk` | string? | 필수첨부파일비고 |
|
||||
| `atchFileGroupId` | string? | 첨부파일그룹아이디 (양식파일) |
|
||||
| `atchFileList` | AtflMngClientResponseDto[]?| 첨부파일 목록 |
|
||||
| `rgtr` | string? | 등록자 UUID |
|
||||
| `rgtrNm` | string? | 등록자명 |
|
||||
| `mdfr` | string? | 수정자 UUID |
|
||||
| `regDt` | string? | 등록일시 (`yyyy-MM-dd HH:mm:ss`) |
|
||||
| `mdfcnDt` | string? | 수정일시 |
|
||||
|
||||
**저장 Request**
|
||||
|
||||
| Field | 필수 | 설명 |
|
||||
| ------------------- | ---- | ------------------ |
|
||||
| `esntlAtchFileId` | Y | 필수첨부파일아이디 |
|
||||
| `docSeComCd` | N | 문서구분 |
|
||||
| `esntlAtchFileNm` | N | 필수첨부파일명 |
|
||||
| `esntlAtchYn` | N | 필수첨부여부 |
|
||||
| `esntlAtchFileRmrk` | N | 비고 |
|
||||
| Field | 필수 | 설명 |
|
||||
| -------------------- | ---- | -------------------- |
|
||||
| `docSeComCd` | Y | 문서구분 |
|
||||
| `esntlAtchFileNm` | N | 필수첨부파일명 |
|
||||
| `esntlAtchYn` | N | 필수첨부여부 |
|
||||
| `esntlAtchFileRvwCn` | N | 필수첨부파일검토내용 |
|
||||
| `esntlAtchFileRmrk` | N | 비고 |
|
||||
| `atchFileGroupId` | N | 첨부파일그룹아이디 |
|
||||
|
||||
**검색 Query**
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { NextConfig } from "next";
|
||||
const subDomain = process.env.NEXT_PUBLIC_SUB_DOMAIN || "http://localhost:13000";
|
||||
|
||||
const webhwpCtrlBase = (
|
||||
process.env.NEXT_PUBLIC_WEBHYP_CTRL_URL || "http://hwp.onasys.co.kr/webhwpctrl"
|
||||
process.env.NEXT_PUBLIC_WEBHYP_CTRL_URL || "https://hwp.onasys.co.kr/webhwpctrl"
|
||||
).replace(/\/$/, "");
|
||||
|
||||
const webhwpAssetPrefixes = ["js", "hwpsdk", "dist", "commonFrame", "wasm"] as const;
|
||||
|
||||
@@ -5,18 +5,6 @@
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>웹한글 기안기 브리지</title>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="http://hwp.onasys.co.kr/webhwpctrl/js/hwpctrlapp/utils/util.js"
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="http://hwp.onasys.co.kr/webhwpctrl/js/hwpctrlapp/hwpCtrlApp.js"
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="http://hwp.onasys.co.kr/webhwpctrl/js/webhwpctrl.js"
|
||||
></script>
|
||||
<style>
|
||||
html,
|
||||
body,
|
||||
@@ -39,27 +27,56 @@
|
||||
<div id="hwpctrl"></div>
|
||||
<script type="text/javascript">
|
||||
(function () {
|
||||
// baseurl: 부모(HangulGiangi)가 NEXT_PUBLIC_WEBHYP_CTRL_URL 값으로 전달.
|
||||
// 없으면 same-origin 프록시 경로(/webhwpctrl/ → next.config rewrites)를 사용한다.
|
||||
var params = new URLSearchParams(window.location.search);
|
||||
var baseUrl = params.get("baseurl") || "http://hwp.onasys.co.kr/webhwpctrl";
|
||||
var baseUrl = params.get("baseurl") || new URL("./", window.location.href).href;
|
||||
if (baseUrl.charAt(baseUrl.length - 1) !== "/") {
|
||||
baseUrl += "/";
|
||||
}
|
||||
|
||||
var HwpCtrl = BuildWebHwpCtrl(
|
||||
"hwpctrl",
|
||||
baseUrl,
|
||||
function () {
|
||||
try {
|
||||
window.parent.postMessage({ type: "hangulgiangi-ready" }, "*");
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
},
|
||||
2,
|
||||
);
|
||||
var scripts = [
|
||||
"js/hwpctrlapp/utils/util.js",
|
||||
"js/hwpctrlapp/hwpCtrlApp.js",
|
||||
"js/webhwpctrl.js",
|
||||
];
|
||||
|
||||
// 부모(HangulGiangi)가 contentWindow.HwpCtrl 로 접근
|
||||
window.HwpCtrl = HwpCtrl;
|
||||
function loadScript(index) {
|
||||
if (index >= scripts.length) {
|
||||
init();
|
||||
return;
|
||||
}
|
||||
var script = document.createElement("script");
|
||||
script.type = "text/javascript";
|
||||
script.src = baseUrl + scripts[index];
|
||||
script.onload = function () {
|
||||
loadScript(index + 1);
|
||||
};
|
||||
script.onerror = function () {
|
||||
console.error("웹한글 기안기 스크립트 로드 실패: " + script.src);
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
|
||||
function init() {
|
||||
var HwpCtrl = BuildWebHwpCtrl(
|
||||
"hwpctrl",
|
||||
baseUrl,
|
||||
function () {
|
||||
try {
|
||||
window.parent.postMessage({ type: "hangulgiangi-ready" }, "*");
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
},
|
||||
2,
|
||||
);
|
||||
|
||||
// 부모(HangulGiangi)가 contentWindow.HwpCtrl 로 접근
|
||||
window.HwpCtrl = HwpCtrl;
|
||||
}
|
||||
|
||||
loadScript(0);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@@ -13,12 +13,16 @@ import type {
|
||||
NowDmgMtncGnsvySearchDto,
|
||||
} from "../model/dc-interface";
|
||||
|
||||
/** 빈 문자열 파라미터를 보내면 서버 오류가 나므로 값이 있는 항목만 전송한다. */
|
||||
async function getNowDmgMtncGnsvy(
|
||||
params: NowDmgMtncGnsvySearchDto,
|
||||
): Promise<NowDmgMtncGnsvyResponseDto[]> {
|
||||
const filledParams = Object.fromEntries(
|
||||
Object.entries(params).filter(([, value]) => value !== undefined && value !== ""),
|
||||
);
|
||||
const response = await client.get<NowDmgMtncGnsvyResponseDto[]>(
|
||||
`${ENDPOINT.DC_SERVICE}/nowDmgMtncGnsvy`,
|
||||
{ params },
|
||||
{ params: filledParams },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ export interface EsntlAtchFileMng {
|
||||
esntlAtchYn: "Y" | "N";
|
||||
/** 필수첨부파일비고 */
|
||||
esntlAtchFileRmrk?: string;
|
||||
/** 필수첨부파일검토내용 */
|
||||
esntlAtchFileRvwCn?: string;
|
||||
/** 첨부파일그룹아이디 (양식파일 그룹) */
|
||||
atchFileGroupId?: string;
|
||||
/** 첨부파일 목록 (양식파일) */
|
||||
@@ -77,6 +79,8 @@ export interface EsntlAtchFileMngSaveRequest {
|
||||
esntlAtchYn?: "Y" | "N";
|
||||
/** 필수첨부파일비고 */
|
||||
esntlAtchFileRmrk?: string;
|
||||
/** 필수첨부파일검토내용 */
|
||||
esntlAtchFileRvwCn?: string;
|
||||
/** 첨부파일그룹아이디 (양식파일 그룹) */
|
||||
atchFileGroupId?: string;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import type { RvwOpnnUpdateRequestDto } from "@/shared/model";
|
||||
|
||||
const BASE = ENDPOINT.AS_SERVICE.FNRS_CNSLTN_RVW_OPNN;
|
||||
|
||||
async function update(
|
||||
fnrsCnsltnDocNo: string,
|
||||
rvwOpnnSn: number,
|
||||
body: RvwOpnnUpdateRequestDto,
|
||||
): Promise<void> {
|
||||
await client.put<void>(`${BASE}/${fnrsCnsltnDocNo}/${rvwOpnnSn}`, body);
|
||||
}
|
||||
|
||||
async function deleteFn(fnrsCnsltnDocNo: string, rvwOpnnSn: number): Promise<void> {
|
||||
await client.delete<void>(`${BASE}/${fnrsCnsltnDocNo}/${rvwOpnnSn}`);
|
||||
}
|
||||
|
||||
export const fnrsCnsltnRvwOpnnService = {
|
||||
update,
|
||||
delete: deleteFn,
|
||||
};
|
||||
@@ -1,65 +1,66 @@
|
||||
export { fnrsCnsltnMngService } from "./api/fnrs-cnsltn-service";
|
||||
export { fnrsCnsltnKeys } from "./api/queries";
|
||||
export {
|
||||
useChangePrgrsSttsComCd,
|
||||
useDeleteFnrsCnsltnMng,
|
||||
useFnrsCnsltnFileVrfcResults,
|
||||
useFnrsCnsltnMng,
|
||||
useFnrsCnsltnMngPage,
|
||||
useRequestActnReviewFnrsCnsltnMng,
|
||||
useRequestExamineFnrsCnsltnMng,
|
||||
useSaveApplyFnrsCnsltnMng,
|
||||
useSaveFnrsCnsltnMng,
|
||||
useSubmitReqSupplementFnrsCnsltnMng,
|
||||
useSubmitSupplementFnrsCnsltnMng,
|
||||
useTechReviewFnrsCnsltnMng,
|
||||
useUpdateFnrsCnsltnMng,
|
||||
} from "./api/queries";
|
||||
export type {
|
||||
BizCstItem,
|
||||
BzScaleItem,
|
||||
CklstResultItem,
|
||||
EsntlAtchFileItem,
|
||||
FnrsCnsltnApplySaveRequestDto,
|
||||
FnrsCnsltnMngSaveRequestDto,
|
||||
FnrsCnsltnRvwOpnnSaveRequestDto,
|
||||
FnrsCnsltnSplmntDmndItem,
|
||||
FnrsSharItem,
|
||||
} from "./model/fnrs-cnsltn-apply-payload-schema";
|
||||
export {
|
||||
fnrsCnsltnApplySaveSchema,
|
||||
fnrsCnsltnBizCstItemSchema,
|
||||
fnrsCnsltnBzScaleItemSchema,
|
||||
fnrsCnsltnCklstResultItemSchema,
|
||||
fnrsCnsltnEsntlAtchFileItemSchema,
|
||||
fnrsCnsltnMngSaveSchema,
|
||||
fnrsCnsltnRvwOpnnSaveSchema,
|
||||
fnrsCnsltnSplmntDmndItemSchema,
|
||||
fnrsSharItemSchema,
|
||||
} from "./model/fnrs-cnsltn-apply-payload-schema";
|
||||
export type {
|
||||
AtflMngClientResponseDto,
|
||||
BizCstTreeDto,
|
||||
BizSclTreeDto,
|
||||
CklstRvwRsltTreeDto,
|
||||
DocPrgrsUpdateRequestDto,
|
||||
ElaprDocPrgrsClientResponseDto,
|
||||
EsntlAtchFileMngTreeResponseDto,
|
||||
FnrsCnsltnFileVrfcRsltResponseDto,
|
||||
FnrsCnsltnMngDetResponseDto,
|
||||
FnrsCnsltnMngResponseDto,
|
||||
FnrsCnsltnMngSearchDto,
|
||||
FnrsCnsltnMngService,
|
||||
FnrsCnsltnMngUpdateRequestDto,
|
||||
FnrsCnsltnRvwOpnnResponseDto,
|
||||
FnrsCnsltnSplmntDmndResponseDto,
|
||||
RagIndexingInfoDto,
|
||||
} from "./model/fnrs-cnsltn-interface";
|
||||
export type {
|
||||
EnvoActnReviewReqDto,
|
||||
FnrsExamineRequestDto,
|
||||
} from "./model/fnrs-cnsltn-payload-schema";
|
||||
export {
|
||||
envoActnReviewReqSchema,
|
||||
fnrsExamineRequestSchema,
|
||||
} from "./model/fnrs-cnsltn-payload-schema";
|
||||
export { fnrsCnsltnRvwOpnnService } from "./api/fnrs-cnsltn-rvw-opnn-service";
|
||||
export { fnrsCnsltnMngService } from "./api/fnrs-cnsltn-service";
|
||||
export { fnrsCnsltnKeys } from "./api/queries";
|
||||
export {
|
||||
useChangePrgrsSttsComCd,
|
||||
useDeleteFnrsCnsltnMng,
|
||||
useFnrsCnsltnFileVrfcResults,
|
||||
useFnrsCnsltnMng,
|
||||
useFnrsCnsltnMngPage,
|
||||
useRequestActnReviewFnrsCnsltnMng,
|
||||
useRequestExamineFnrsCnsltnMng,
|
||||
useSaveApplyFnrsCnsltnMng,
|
||||
useSaveFnrsCnsltnMng,
|
||||
useSubmitReqSupplementFnrsCnsltnMng,
|
||||
useSubmitSupplementFnrsCnsltnMng,
|
||||
useTechReviewFnrsCnsltnMng,
|
||||
useUpdateFnrsCnsltnMng,
|
||||
} from "./api/queries";
|
||||
export type {
|
||||
BizCstItem,
|
||||
BzScaleItem,
|
||||
CklstResultItem,
|
||||
EsntlAtchFileItem,
|
||||
FnrsCnsltnApplySaveRequestDto,
|
||||
FnrsCnsltnMngSaveRequestDto,
|
||||
FnrsCnsltnRvwOpnnSaveRequestDto,
|
||||
FnrsCnsltnSplmntDmndItem,
|
||||
FnrsSharItem,
|
||||
} from "./model/fnrs-cnsltn-apply-payload-schema";
|
||||
export {
|
||||
fnrsCnsltnApplySaveSchema,
|
||||
fnrsCnsltnBizCstItemSchema,
|
||||
fnrsCnsltnBzScaleItemSchema,
|
||||
fnrsCnsltnCklstResultItemSchema,
|
||||
fnrsCnsltnEsntlAtchFileItemSchema,
|
||||
fnrsCnsltnMngSaveSchema,
|
||||
fnrsCnsltnRvwOpnnSaveSchema,
|
||||
fnrsCnsltnSplmntDmndItemSchema,
|
||||
fnrsSharItemSchema,
|
||||
} from "./model/fnrs-cnsltn-apply-payload-schema";
|
||||
export type {
|
||||
AtflMngClientResponseDto,
|
||||
BizCstTreeDto,
|
||||
BizSclTreeDto,
|
||||
CklstRvwRsltTreeDto,
|
||||
DocPrgrsUpdateRequestDto,
|
||||
ElaprDocPrgrsClientResponseDto,
|
||||
EsntlAtchFileMngTreeResponseDto,
|
||||
FnrsCnsltnFileVrfcRsltResponseDto,
|
||||
FnrsCnsltnMngDetResponseDto,
|
||||
FnrsCnsltnMngResponseDto,
|
||||
FnrsCnsltnMngSearchDto,
|
||||
FnrsCnsltnMngService,
|
||||
FnrsCnsltnMngUpdateRequestDto,
|
||||
FnrsCnsltnRvwOpnnResponseDto,
|
||||
FnrsCnsltnSplmntDmndResponseDto,
|
||||
RagIndexingInfoDto,
|
||||
} from "./model/fnrs-cnsltn-interface";
|
||||
export type {
|
||||
EnvoActnReviewReqDto,
|
||||
FnrsExamineRequestDto,
|
||||
} from "./model/fnrs-cnsltn-payload-schema";
|
||||
export {
|
||||
envoActnReviewReqSchema,
|
||||
fnrsExamineRequestSchema,
|
||||
} from "./model/fnrs-cnsltn-payload-schema";
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import type { RvwOpnnUpdateRequestDto } from "@/shared/model";
|
||||
|
||||
const BASE = ENDPOINT.AS_SERVICE.INSTL_SNCN_RVW_OPNN;
|
||||
|
||||
async function update(
|
||||
instlSncnDocNo: string,
|
||||
rvwOpnnSn: number,
|
||||
body: RvwOpnnUpdateRequestDto,
|
||||
): Promise<void> {
|
||||
await client.put<void>(`${BASE}/${instlSncnDocNo}/${rvwOpnnSn}`, body);
|
||||
}
|
||||
|
||||
async function deleteFn(instlSncnDocNo: string, rvwOpnnSn: number): Promise<void> {
|
||||
await client.delete<void>(`${BASE}/${instlSncnDocNo}/${rvwOpnnSn}`);
|
||||
}
|
||||
|
||||
export const instlSncnRvwOpnnService = {
|
||||
update,
|
||||
delete: deleteFn,
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
export { instlSncnRvwOpnnService } from "./api/instl-sncn-rvw-opnn-service";
|
||||
export { instlSncnRegionService, instlSncnService } from "./api/instl-sncn-service";
|
||||
export {
|
||||
instlSncnKeys,
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import type { RvwOpnnUpdateRequestDto } from "@/shared/model";
|
||||
|
||||
const BASE = ENDPOINT.AS_SERVICE.SEWER_BSC_PLAN_RVW_OPNNS;
|
||||
|
||||
async function update(
|
||||
sewerBscPlanDocNo: string,
|
||||
chgCycl: number,
|
||||
rvwOpnnSn: number,
|
||||
body: RvwOpnnUpdateRequestDto,
|
||||
): Promise<void> {
|
||||
await client.put<void>(`${BASE}/${sewerBscPlanDocNo}/${chgCycl}/${rvwOpnnSn}`, body);
|
||||
}
|
||||
|
||||
async function deleteFn(sewerBscPlanDocNo: string, chgCycl: number, rvwOpnnSn: number): Promise<void> {
|
||||
await client.delete<void>(`${BASE}/${sewerBscPlanDocNo}/${chgCycl}/${rvwOpnnSn}`);
|
||||
}
|
||||
|
||||
export const sewerBscPlanRvwOpnnService = {
|
||||
update,
|
||||
delete: deleteFn,
|
||||
};
|
||||
@@ -1,64 +1,65 @@
|
||||
export { sewerBscPlanKeys } from "./api/queries";
|
||||
export {
|
||||
useDeleteSewerBscPlan,
|
||||
useSaveApplySewerBscPlan,
|
||||
useSaveSewerBscPlan,
|
||||
useSewerBscPlan,
|
||||
useSewerBscPlanDetails,
|
||||
useSewerBscPlanPage,
|
||||
useSubmitReqSupplementSewerBscPlan,
|
||||
useSubmitSupplementSewerBscPlan,
|
||||
useTechReviewSewerBscPlan,
|
||||
useUpdateSewerBscPlan,
|
||||
} from "./api/queries";
|
||||
export { sewerBscPlanService } from "./api/sewer-bsc-plan-service";
|
||||
export type {
|
||||
CklstResultItem,
|
||||
EsntlAtchFileItem,
|
||||
SewerBscPlanApplySaveRequestDto,
|
||||
SewerBscPlanBizCstItem,
|
||||
SewerBscPlanDtlSaveRequestDto,
|
||||
SewerBscPlanFnrsCstItem,
|
||||
SewerBscPlanOtlnItem,
|
||||
SewerBscPlanRvwOpnnItem,
|
||||
SewerBscPlanSaveRequestDto,
|
||||
SewerBscPlanSplmntDmndItem,
|
||||
SewerBscPlanTmplntDtlItem,
|
||||
SewerBscPlanTmplntItem,
|
||||
SewerBscPlanTmplntTmprStrgItem,
|
||||
} from "./model/sewer-bsc-plan-apply-payload-schema";
|
||||
export {
|
||||
sewerBscPlanApplySaveSchema,
|
||||
sewerBscPlanBizCstItemSchema,
|
||||
sewerBscPlanCklstResultItemSchema,
|
||||
sewerBscPlanDtlSaveSchema,
|
||||
sewerBscPlanEsntlAtchFileItemSchema,
|
||||
sewerBscPlanFnrsCstItemSchema,
|
||||
sewerBscPlanOtlnItemSchema,
|
||||
sewerBscPlanRvwOpnnItemSchema,
|
||||
sewerBscPlanSaveSchema,
|
||||
sewerBscPlanSplmntDmndItemSchema,
|
||||
sewerBscPlanTmplntDtlItemSchema,
|
||||
sewerBscPlanTmplntItemSchema,
|
||||
sewerBscPlanTmplntTmprStrgItemSchema,
|
||||
} from "./model/sewer-bsc-plan-apply-payload-schema";
|
||||
export type {
|
||||
AtflMngClientResponseDto,
|
||||
CklstRvwRsltTreeDto,
|
||||
EsntlAtchFileMngTreeResponseDto,
|
||||
SewerBscPlanApplySaveResponseDto,
|
||||
SewerBscPlanBizCstResponseDto,
|
||||
SewerBscPlanDetResponseDto,
|
||||
SewerBscPlanDtlResponseDto,
|
||||
SewerBscPlanFnrsCstResponseDto,
|
||||
SewerBscPlanOtlnResponseDto,
|
||||
SewerBscPlanResponseDto,
|
||||
SewerBscPlanRvwOpnnResponseDto,
|
||||
SewerBscPlanSearchRequestDto,
|
||||
SewerBscPlanService,
|
||||
SewerBscPlanSplmntDmndResponseDto,
|
||||
SewerBscPlanTmplntDtlResponseDto,
|
||||
SewerBscPlanTmplntResponseDto,
|
||||
SewerBscPlanTmplntTmprStrgResponseDto,
|
||||
SewerBscPlanUpdateRequestDto,
|
||||
} from "./model/sewer-bsc-plan-interface";
|
||||
export { sewerBscPlanKeys } from "./api/queries";
|
||||
export {
|
||||
useDeleteSewerBscPlan,
|
||||
useSaveApplySewerBscPlan,
|
||||
useSaveSewerBscPlan,
|
||||
useSewerBscPlan,
|
||||
useSewerBscPlanDetails,
|
||||
useSewerBscPlanPage,
|
||||
useSubmitReqSupplementSewerBscPlan,
|
||||
useSubmitSupplementSewerBscPlan,
|
||||
useTechReviewSewerBscPlan,
|
||||
useUpdateSewerBscPlan,
|
||||
} from "./api/queries";
|
||||
export { sewerBscPlanRvwOpnnService } from "./api/sewer-bsc-plan-rvw-opnn-service";
|
||||
export { sewerBscPlanService } from "./api/sewer-bsc-plan-service";
|
||||
export type {
|
||||
CklstResultItem,
|
||||
EsntlAtchFileItem,
|
||||
SewerBscPlanApplySaveRequestDto,
|
||||
SewerBscPlanBizCstItem,
|
||||
SewerBscPlanDtlSaveRequestDto,
|
||||
SewerBscPlanFnrsCstItem,
|
||||
SewerBscPlanOtlnItem,
|
||||
SewerBscPlanRvwOpnnItem,
|
||||
SewerBscPlanSaveRequestDto,
|
||||
SewerBscPlanSplmntDmndItem,
|
||||
SewerBscPlanTmplntDtlItem,
|
||||
SewerBscPlanTmplntItem,
|
||||
SewerBscPlanTmplntTmprStrgItem,
|
||||
} from "./model/sewer-bsc-plan-apply-payload-schema";
|
||||
export {
|
||||
sewerBscPlanApplySaveSchema,
|
||||
sewerBscPlanBizCstItemSchema,
|
||||
sewerBscPlanCklstResultItemSchema,
|
||||
sewerBscPlanDtlSaveSchema,
|
||||
sewerBscPlanEsntlAtchFileItemSchema,
|
||||
sewerBscPlanFnrsCstItemSchema,
|
||||
sewerBscPlanOtlnItemSchema,
|
||||
sewerBscPlanRvwOpnnItemSchema,
|
||||
sewerBscPlanSaveSchema,
|
||||
sewerBscPlanSplmntDmndItemSchema,
|
||||
sewerBscPlanTmplntDtlItemSchema,
|
||||
sewerBscPlanTmplntItemSchema,
|
||||
sewerBscPlanTmplntTmprStrgItemSchema,
|
||||
} from "./model/sewer-bsc-plan-apply-payload-schema";
|
||||
export type {
|
||||
AtflMngClientResponseDto,
|
||||
CklstRvwRsltTreeDto,
|
||||
EsntlAtchFileMngTreeResponseDto,
|
||||
SewerBscPlanApplySaveResponseDto,
|
||||
SewerBscPlanBizCstResponseDto,
|
||||
SewerBscPlanDetResponseDto,
|
||||
SewerBscPlanDtlResponseDto,
|
||||
SewerBscPlanFnrsCstResponseDto,
|
||||
SewerBscPlanOtlnResponseDto,
|
||||
SewerBscPlanResponseDto,
|
||||
SewerBscPlanRvwOpnnResponseDto,
|
||||
SewerBscPlanSearchRequestDto,
|
||||
SewerBscPlanService,
|
||||
SewerBscPlanSplmntDmndResponseDto,
|
||||
SewerBscPlanTmplntDtlResponseDto,
|
||||
SewerBscPlanTmplntResponseDto,
|
||||
SewerBscPlanTmplntTmprStrgResponseDto,
|
||||
SewerBscPlanUpdateRequestDto,
|
||||
} from "./model/sewer-bsc-plan-interface";
|
||||
|
||||
@@ -157,3 +157,33 @@
|
||||
border-color: #96c3ff;
|
||||
box-shadow: 0 4px 12px rgba(0, 107, 248, 0.3);
|
||||
}
|
||||
|
||||
/* 블랙 스킨 (onasys 디자인 design/c 색상 기준) */
|
||||
:global([data-theme="black"]) .basinLabel {
|
||||
background: #404244ee;
|
||||
border-color: #5e6165;
|
||||
color: #f1f1f1;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
:global([data-theme="black"]) .basinLabel.active {
|
||||
background: #0056b3;
|
||||
border-color: #96c3ff;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
:global([data-theme="black"]) .popup {
|
||||
background: #404244;
|
||||
border-color: #4e5154;
|
||||
color: #f1f1f1;
|
||||
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
:global([data-theme="black"]) .popup > div {
|
||||
border-color: #4e5154;
|
||||
}
|
||||
|
||||
:global([data-theme="black"]) .popup > div:first-child {
|
||||
background: #4e5154;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@@ -40,12 +40,12 @@ export function DashboardNotiList({
|
||||
);
|
||||
|
||||
return (
|
||||
<section className="card card-border m-0 bg-[#FAFBFC]">
|
||||
<section className="card card-border m-0">
|
||||
<div className="card-header flex items-center justify-between gap-4">
|
||||
<h3 className="text-2xl font-semibold">
|
||||
알림이력
|
||||
{dataUpdatedAt > 0 ? (
|
||||
<span className="pl-2 text-lg font-medium text-[#4A4A4A]">
|
||||
<span className="pl-2 text-lg font-medium text-label">
|
||||
(업데이트 일시: {dayjs(dataUpdatedAt).format("YYYY-MM-DD HH:mm")})
|
||||
</span>
|
||||
) : null}
|
||||
@@ -75,7 +75,7 @@ export function DashboardNotiList({
|
||||
) : null}
|
||||
|
||||
{isError ? (
|
||||
<p className="flex min-h-[12rem] items-center justify-center text-[#4A4A4A]">
|
||||
<p className="flex min-h-[12rem] items-center justify-center text-label">
|
||||
알림이력을 불러오지 못했습니다.
|
||||
</p>
|
||||
) : null}
|
||||
@@ -110,7 +110,7 @@ interface DashboardNotiRowProps {
|
||||
function DashboardNotiRow({ row, isActionPending, onAction }: DashboardNotiRowProps) {
|
||||
return (
|
||||
<div className="grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-4 border-b px-2 py-4 last:border-b-0">
|
||||
<span className="text-lg text-[#4A4A4A]">
|
||||
<span className="text-lg text-label">
|
||||
{row.regDt ? dayjs(row.regDt).format("YYYY-MM-DD") : "-"}
|
||||
</span>
|
||||
<p className="min-w-0 line-clamp-2 text-xl sm:line-clamp-1">{row.notiTtl}</p>
|
||||
|
||||
@@ -46,7 +46,7 @@ export function DashboardNoticeTable() {
|
||||
});
|
||||
|
||||
return (
|
||||
<section className="card card-border m-0 bg-[#FAFBFC]">
|
||||
<section className="card card-border m-0">
|
||||
<div className="card-header flex items-center justify-between">
|
||||
<h3 className="text-2xl font-semibold">공지사항</h3>
|
||||
<Button type="button" size="sm" color="white" className="!text-[#0056B3]" onClick={handleMoreClick}>
|
||||
|
||||
@@ -11,7 +11,8 @@ export function DashboardPage() {
|
||||
const { data, isPending, isError, dataUpdatedAt } = useGetMainDashboard();
|
||||
|
||||
return (
|
||||
<div className="flex min-h-full gap-4 bg-[#E9EDF2] px-[1.8rem] pb-6 pt-4 text-[#1E1E1E]">
|
||||
// 배경·글자색은 테마 변수를 따름 (배경 --container, 글자 --foreground)
|
||||
<div className="flex min-h-full gap-4 bg-container px-[1.8rem] pb-6 pt-4 text-foreground">
|
||||
<div className="hidden flex-[0.5] sm:block">
|
||||
<NationalAsMap basinDocPrgrsList={data?.basinDocPrgrsList} />
|
||||
</div>
|
||||
|
||||
@@ -24,7 +24,7 @@ export function DocPrgrsSttsCards({ items, isPending, isError }: DocPrgrsSttsCar
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<div className="card card-border m-0 flex min-h-[14rem] items-center justify-center bg-[#FAFBFC] text-[#4A4A4A]">
|
||||
<div className="card card-border m-0 flex min-h-[14rem] items-center justify-center text-label">
|
||||
진행상황을 불러오지 못했습니다.
|
||||
</div>
|
||||
);
|
||||
@@ -39,7 +39,7 @@ export function DocPrgrsSttsCards({ items, isPending, isError }: DocPrgrsSttsCar
|
||||
return (
|
||||
<div
|
||||
key={slot.docSeComCd}
|
||||
className="card card-border hover:bg-muted/40 m-0 block bg-[#FAFBFC] transition-colors"
|
||||
className="card card-border hover:bg-muted/40 m-0 block transition-colors"
|
||||
>
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-semibold">{slot.title}</h3>
|
||||
@@ -90,7 +90,7 @@ function DocPrgrsSttsMetric({
|
||||
>
|
||||
<Icon className="size-6" />
|
||||
</span>
|
||||
<span className="text-xl text-[#4A4A4A]">{label}</span>
|
||||
<span className="text-xl text-label">{label}</span>
|
||||
<span className="text-3xl font-semibold tabular-nums">{count}</span>
|
||||
</div>
|
||||
);
|
||||
@@ -108,21 +108,22 @@ const STAT_METRICS = [
|
||||
field: "reqCnt",
|
||||
label: "신청",
|
||||
iconClassName: "bg-[#4285F4]",
|
||||
bgColor: "bg-blue-100",
|
||||
// 블랙 스킨: 아이콘 색 반투명 배경
|
||||
bgColor: "bg-blue-100 [[data-theme=black]_&]:bg-[rgba(66,133,244,0.18)]",
|
||||
Icon: FilePlus,
|
||||
},
|
||||
{
|
||||
field: "prgrsCnt",
|
||||
label: "진행",
|
||||
iconClassName: "bg-[#34A853]",
|
||||
bgColor: "bg-emerald-100",
|
||||
bgColor: "bg-emerald-100 [[data-theme=black]_&]:bg-[rgba(52,168,83,0.18)]",
|
||||
Icon: RefreshCw,
|
||||
},
|
||||
{
|
||||
field: "cmptnCnt",
|
||||
label: "완료",
|
||||
iconClassName: "bg-[#EA4335]",
|
||||
bgColor: "bg-orange-100",
|
||||
bgColor: "bg-orange-100 [[data-theme=black]_&]:bg-[rgba(234,67,53,0.18)]",
|
||||
Icon: CircleCheck,
|
||||
},
|
||||
] as const;
|
||||
|
||||
@@ -18,7 +18,7 @@ export function NationalAsMap({ basinDocPrgrsList }: NationalAsMapProps) {
|
||||
const popupColumnSpan = Math.max(1, popupContent.items.length);
|
||||
|
||||
return (
|
||||
<section className="card card-border m-0 flex h-full min-h-[40rem] flex-col bg-[#FAFBFC]">
|
||||
<section className="card card-border m-0 flex h-full min-h-[40rem] flex-col">
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-semibold">전국 행정지원 지도</h3>
|
||||
</div>
|
||||
|
||||
@@ -1,169 +1,183 @@
|
||||
"use client";
|
||||
|
||||
import { Fragment } from "react";
|
||||
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
|
||||
import { dayjs } from "@/shared/lib";
|
||||
import type { DesignCnstnMngResponseDto } from "@/entities/design-cnstn";
|
||||
import { Badge } from "@common/components";
|
||||
|
||||
const columnHelper = createColumnHelper<DesignCnstnMngResponseDto>();
|
||||
|
||||
const MAIN_STEP_LABELS = {
|
||||
apply: "설계자문 신청",
|
||||
techReview: "기술검토",
|
||||
reply: "검토결과 회신",
|
||||
} as const;
|
||||
|
||||
export const PROGRESS_STATUS_COLUMNS = [
|
||||
{ label: "설계자문 신청 (시군)", sttsComCd: "CT" },
|
||||
{ label: "기술검토", sttsComCd: "CS" },
|
||||
{ label: "기술검토 회신", sttsComCd: "CP" },
|
||||
];
|
||||
|
||||
type BadgeState = "completed" | "current" | "pending";
|
||||
|
||||
interface ProgressBadge {
|
||||
label: string;
|
||||
state: BadgeState;
|
||||
}
|
||||
|
||||
function buildProgressBadges(prgrsSttsComCd: string): ProgressBadge[] {
|
||||
if (prgrsSttsComCd === "CV") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
if (prgrsSttsComCd === "CT" || prgrsSttsComCd === "CS" || prgrsSttsComCd === "SL") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "current" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
if (prgrsSttsComCd === "CP") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "completed" },
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
function getProgressStatusBadge(prgrsSttsComCd?: string | null) {
|
||||
if (prgrsSttsComCd === "SL") {
|
||||
return <Badge color="orange">보완요청</Badge>;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getBadgeColor(state: BadgeState) {
|
||||
switch (state) {
|
||||
case "completed":
|
||||
return "green";
|
||||
case "current":
|
||||
return "blue";
|
||||
case "pending":
|
||||
return "lightGray";
|
||||
}
|
||||
}
|
||||
|
||||
function getBadgeStateText(state: BadgeState) {
|
||||
switch (state) {
|
||||
case "completed":
|
||||
return "완료";
|
||||
case "current":
|
||||
return "진행중";
|
||||
case "pending":
|
||||
return "대기";
|
||||
}
|
||||
}
|
||||
|
||||
function formatOptional(value: string | null | undefined) {
|
||||
return value?.trim() ? value : "-";
|
||||
}
|
||||
|
||||
interface ProgressStepBadgeProps {
|
||||
prgrsSttsComCd: string;
|
||||
}
|
||||
|
||||
function ProgressStepBadge({ prgrsSttsComCd }: ProgressStepBadgeProps) {
|
||||
const badges = buildProgressBadges(prgrsSttsComCd);
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-center justify-start">
|
||||
{badges.map((badge, index) => (
|
||||
<Fragment key={`${badge.label}-${index}`}>
|
||||
{index > 0 && <span className="mx-2">></span>}
|
||||
<Badge color={getBadgeColor(badge.state)}>
|
||||
{badge.label}({getBadgeStateText(badge.state)})
|
||||
</Badge>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function createDesignCnstnColumns() {
|
||||
return [
|
||||
columnHelper.accessor("sido", {
|
||||
header: "시도",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
}),
|
||||
columnHelper.accessor("gugun", {
|
||||
header: "시군구",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
}),
|
||||
columnHelper.accessor("bizNm", {
|
||||
header: "사업명",
|
||||
meta: { align: "left" },
|
||||
cell: ({ row, getValue }) => (
|
||||
<>
|
||||
{getProgressStatusBadge(row.original.prgrsSttsComCd)} {formatOptional(getValue())}
|
||||
</>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("taskSeCdNm", {
|
||||
header: "업무구분",
|
||||
meta: { align: "center" },
|
||||
cell: ({ row }) => formatOptional(row.original.taskSeCdNm || row.original.taskSeComCd),
|
||||
}),
|
||||
columnHelper.accessor("aplcntNm", {
|
||||
header: "담당자",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
}),
|
||||
columnHelper.accessor("envoSeCdNm", {
|
||||
header: "해당기관",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: "progress",
|
||||
header: "진행상황",
|
||||
meta: { align: "center" },
|
||||
cell: ({ row }) => <ProgressStepBadge prgrsSttsComCd={row.original.prgrsSttsComCd || ""} />,
|
||||
}),
|
||||
columnHelper.accessor("regDt", {
|
||||
header: "신청일",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => {
|
||||
const value = getValue();
|
||||
return value ? dayjs(value).format("YYYY-MM-DD") : "-";
|
||||
},
|
||||
}),
|
||||
];
|
||||
}
|
||||
"use client";
|
||||
|
||||
import { Fragment } from "react";
|
||||
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
|
||||
import { dayjs } from "@/shared/lib";
|
||||
import type { DesignCnstnMngResponseDto } from "@/entities/design-cnstn";
|
||||
import { Badge } from "@common/components";
|
||||
|
||||
const columnHelper = createColumnHelper<DesignCnstnMngResponseDto>();
|
||||
|
||||
const MAIN_STEP_LABELS = {
|
||||
apply: "설계자문 신청",
|
||||
techReview: "기술검토",
|
||||
reply: "검토결과 회신",
|
||||
} as const;
|
||||
|
||||
export const PROGRESS_STATUS_COLUMNS = [
|
||||
{ label: "설계자문 신청 (시군)", sttsComCd: "CT" },
|
||||
{ label: "기술검토", sttsComCd: "CS" },
|
||||
{ label: "기술검토 회신", sttsComCd: "CP" },
|
||||
];
|
||||
|
||||
type BadgeState = "completed" | "current" | "pending";
|
||||
|
||||
interface ProgressBadge {
|
||||
label: string;
|
||||
state: BadgeState;
|
||||
}
|
||||
|
||||
function buildProgressBadges(prgrsSttsComCd: string): ProgressBadge[] {
|
||||
if (prgrsSttsComCd === "CV") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
if (prgrsSttsComCd === "CT" || prgrsSttsComCd === "CS" || prgrsSttsComCd === "SL") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "current" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
if (prgrsSttsComCd === "CP") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "completed" },
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
function getProgressStatusBadge(prgrsSttsComCd?: string | null) {
|
||||
if (prgrsSttsComCd === "SL") {
|
||||
return <Badge color="orange">보완요청</Badge>;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getBadgeColor(state: BadgeState) {
|
||||
switch (state) {
|
||||
case "completed":
|
||||
return "green";
|
||||
case "current":
|
||||
return "blue";
|
||||
case "pending":
|
||||
return "lightGray";
|
||||
}
|
||||
}
|
||||
|
||||
function getBadgeStateText(state: BadgeState) {
|
||||
switch (state) {
|
||||
case "completed":
|
||||
return "완료";
|
||||
case "current":
|
||||
return "진행중";
|
||||
case "pending":
|
||||
return "대기";
|
||||
}
|
||||
}
|
||||
|
||||
/** 단계명을 2줄로 분리: "설치인가 신청" → ["설치인가", "신청"], "기술검토" → ["기술검토", ""] */
|
||||
function splitStepLabel(label: string) {
|
||||
const [title = "", ...rest] = label.split(" ");
|
||||
return { title, detail: rest.join(" ") };
|
||||
}
|
||||
|
||||
function formatOptional(value: string | null | undefined) {
|
||||
return value?.trim() ? value : "-";
|
||||
}
|
||||
|
||||
interface ProgressStepBadgeProps {
|
||||
prgrsSttsComCd: string;
|
||||
}
|
||||
|
||||
function ProgressStepBadge({ prgrsSttsComCd }: ProgressStepBadgeProps) {
|
||||
const badges = buildProgressBadges(prgrsSttsComCd);
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-center justify-center">
|
||||
{badges.map((badge, index) => (
|
||||
<Fragment key={`${badge.label}-${index}`}>
|
||||
{index > 0 && <span className="mx-2">></span>}
|
||||
{/* 1줄: 단계명 / 2줄: 세부단계 + 상태 (예: 설치인가 / 신청완료, 기술검토 / 완료) */}
|
||||
<Badge
|
||||
color={getBadgeColor(badge.state)}
|
||||
className="w-[100px] flex-col justify-center !px-0 py-1 text-center leading-tight"
|
||||
>
|
||||
<span>{splitStepLabel(badge.label).title}</span>
|
||||
<span>
|
||||
{splitStepLabel(badge.label).detail}
|
||||
{getBadgeStateText(badge.state)}
|
||||
</span>
|
||||
</Badge>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function createDesignCnstnColumns() {
|
||||
return [
|
||||
columnHelper.accessor("sido", {
|
||||
header: "시도",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
}),
|
||||
columnHelper.accessor("gugun", {
|
||||
header: "시군구",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
}),
|
||||
columnHelper.accessor("bizNm", {
|
||||
header: "사업명",
|
||||
meta: { align: "left" },
|
||||
cell: ({ row, getValue }) => (
|
||||
<>
|
||||
{getProgressStatusBadge(row.original.prgrsSttsComCd)} {formatOptional(getValue())}
|
||||
</>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("taskSeCdNm", {
|
||||
header: "업무구분",
|
||||
meta: { align: "center" },
|
||||
cell: ({ row }) => formatOptional(row.original.taskSeCdNm || row.original.taskSeComCd),
|
||||
}),
|
||||
columnHelper.accessor("aplcntNm", {
|
||||
header: "담당자",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
}),
|
||||
columnHelper.accessor("envoSeCdNm", {
|
||||
header: "해당기관",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: "progress",
|
||||
header: "진행상황",
|
||||
meta: { align: "center" },
|
||||
cell: ({ row }) => <ProgressStepBadge prgrsSttsComCd={row.original.prgrsSttsComCd || ""} />,
|
||||
}),
|
||||
columnHelper.accessor("regDt", {
|
||||
header: "신청일",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => {
|
||||
const value = getValue();
|
||||
return value ? dayjs(value).format("YYYY-MM-DD") : "-";
|
||||
},
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -77,8 +77,8 @@ function DesignListPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4 px-[1.8rem] pb-6">
|
||||
<section className="card card-border mx-0 bg-background">
|
||||
<div className="card card-border !mt-6">
|
||||
<div className="card card-border">
|
||||
<form className="flex flex-wrap items-center gap-4 p-2" onSubmit={handleSearch}>
|
||||
<div className="flex items-center gap-2">
|
||||
<Label htmlFor="sidoComCd" className="whitespace-nowrap">
|
||||
@@ -145,7 +145,7 @@ function DesignListPage() {
|
||||
</div>
|
||||
<Button type="submit">조회</Button>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<DesignList
|
||||
searchParams={searchParams}
|
||||
|
||||
@@ -78,15 +78,15 @@ function DesignList({ searchParams, pagination, onPaginationChange }: DesignList
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className="card card-border mx-0 bg-background pb-8 pt-0">
|
||||
<div className="card card-border pt-0">
|
||||
<div className="card-header flex items-center justify-between">
|
||||
<h3 className="text-2xl font-semibold">설계자문 목록</h3>
|
||||
<h3 className="text-2xl font-medium text-label">설계자문 목록</h3>
|
||||
<Button type="button" color="green" onClick={() => setIsTaskSeDialogOpen(true)}>
|
||||
신청
|
||||
</Button>
|
||||
</div>
|
||||
{tableComponent}
|
||||
</section>
|
||||
<div className="card !m-[1.2rem] !p-0">{tableComponent}</div>
|
||||
</div>
|
||||
<PbadmsTaskApplyDialog
|
||||
open={isTaskSeDialogOpen}
|
||||
onOpenChange={setIsTaskSeDialogOpen}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { SectionCollapsible } from "@/shared/ui";
|
||||
import { Field, FieldError, FieldGroup } from "@/shared/ui/field";
|
||||
import { findCodeLabel, useCodeList } from "@/entities/code";
|
||||
import { type BnsApplyData, BnsPickerDialog } from "@/widgets/bns-picker";
|
||||
import { PreviousPlanLookupButton } from "@/widgets/previous-plan-lookup";
|
||||
import { Input, Table, TableCell, TableHead, TableHeader, TableRow } from "@common/components";
|
||||
|
||||
import type { DesignCnstnRegistFormData } from "../../lib/design-cnstn-regist-schema";
|
||||
@@ -16,9 +17,9 @@ function BasicInfoFormSection() {
|
||||
const { control, watch, setValue } = useFormContext<DesignCnstnRegistFormData>();
|
||||
const [isPickerOpen, setIsPickerOpen] = useState(false);
|
||||
const taskSeComCd = watch("designCnstnMng.taskSeComCd");
|
||||
const localGovLabel = [watch("designCnstnMng.sido"), watch("designCnstnMng.gugun")]
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
const sido = watch("designCnstnMng.sido");
|
||||
const gugun = watch("designCnstnMng.gugun");
|
||||
const localGovLabel = [sido, gugun].filter(Boolean).join(" ");
|
||||
const { data: taskOptions } = useCodeList("CNFM");
|
||||
|
||||
function handleBnsSelect(data: BnsApplyData) {
|
||||
@@ -36,6 +37,7 @@ function BasicInfoFormSection() {
|
||||
|
||||
return (
|
||||
<SectionCollapsible title="기본 정보">
|
||||
<PreviousPlanLookupButton sidoNm={sido} gugunNm={gugun} />
|
||||
<Table caption="기본 정보 입력으로 사업명, 사업기간, 사업내용, 구분, 신청 지자체 정보 제공" variant="secondary">
|
||||
<colgroup>
|
||||
<col width="15%" />
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { SectionCollapsible } from "@/shared/ui";
|
||||
import type { DesignCnstnMngResponseDto } from "@/entities/design-cnstn";
|
||||
import { PreviousPlanLookupButton } from "@/widgets/previous-plan-lookup";
|
||||
import { Table, TableCell, TableHead, TableHeader, TableRow } from "@common/components";
|
||||
|
||||
interface BasicInfoSectionProps {
|
||||
@@ -11,6 +12,7 @@ interface BasicInfoSectionProps {
|
||||
function BasicInfoSection({ det }: BasicInfoSectionProps) {
|
||||
return (
|
||||
<SectionCollapsible title="기본 정보">
|
||||
<PreviousPlanLookupButton sidoNm={det.sido} gugunNm={det.gugun} />
|
||||
<Table caption="기본 정보로 사업명, 사업기간, 사업내용, 환경청구분, 구분, 신청지자체 정보 제공" variant="secondary">
|
||||
<colgroup>
|
||||
<col width="15%" />
|
||||
|
||||
@@ -1,338 +1,334 @@
|
||||
"use client";
|
||||
|
||||
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
|
||||
|
||||
import { dayjs, getDefaultFileKey, useTus } from "@/shared/lib";
|
||||
import { TextShimmer } from "@/shared/ui";
|
||||
import { SectionCollapsible } from "@/shared/ui";
|
||||
import { Field, FieldLabel, FieldSet } from "@/shared/ui/field";
|
||||
import { AttachmentDownloadLink, attachService } from "@/entities/attach";
|
||||
import type { DesignCnstnRvwOpnnResponseDto } from "@/entities/design-cnstn";
|
||||
import {
|
||||
Button,
|
||||
RadioGroup,
|
||||
RadioGroupItem,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
Textarea,
|
||||
} from "@common/components";
|
||||
|
||||
import type {
|
||||
ReviewOpinionFormData,
|
||||
ReviewOpinionGetFormDataResult,
|
||||
} from "../../lib/review-opinion-utils";
|
||||
import type { ReviewStepCode } from "../../model/view-types";
|
||||
import { REVIEW_STEP_LABELS } from "../../model/view-types";
|
||||
|
||||
const RVW_OPNN_FILE_KEY_PREFIX = "rvw-opnn::";
|
||||
|
||||
interface TechReviewOpinionSectionProps {
|
||||
rvwList: DesignCnstnRvwOpnnResponseDto[];
|
||||
editableReviewStep?: ReviewStepCode;
|
||||
atrzSeComCdList?: string[];
|
||||
isAtrzLoading?: boolean;
|
||||
}
|
||||
|
||||
export interface TechReviewOpinionSectionHandle {
|
||||
getFormData: () => ReviewOpinionGetFormDataResult | null;
|
||||
resetForm: () => void;
|
||||
}
|
||||
|
||||
interface TechReviewOpinionTableProps {
|
||||
items: DesignCnstnRvwOpnnResponseDto[];
|
||||
}
|
||||
|
||||
interface UploadedOpinionFile {
|
||||
fileName: string;
|
||||
fileKey: string;
|
||||
atflId?: string;
|
||||
atflCd?: string;
|
||||
}
|
||||
|
||||
const EMPTY_FORM_DATA: ReviewOpinionFormData = {
|
||||
rvwRsltCn: "",
|
||||
rvwRsltAnmlyYn: "",
|
||||
atchFileGroupId: "",
|
||||
opnrSeComCd: "",
|
||||
};
|
||||
|
||||
function createRvwOpnnUploadKey(file: File) {
|
||||
return `${RVW_OPNN_FILE_KEY_PREFIX}${getDefaultFileKey(file)}`;
|
||||
}
|
||||
|
||||
function hasFormContent(formData: ReviewOpinionFormData) {
|
||||
return Boolean(formData.rvwRsltCn.trim() || formData.rvwRsltAnmlyYn || formData.atchFileGroupId);
|
||||
}
|
||||
|
||||
const TechReviewOpinionSection = forwardRef<
|
||||
TechReviewOpinionSectionHandle,
|
||||
TechReviewOpinionSectionProps
|
||||
>(function TechReviewOpinionSection(
|
||||
{ rvwList, editableReviewStep, atrzSeComCdList = [], isAtrzLoading = false },
|
||||
ref,
|
||||
) {
|
||||
const canWriteOpinion = !isAtrzLoading && atrzSeComCdList.includes("ATRZ02");
|
||||
const [formData, setFormData] = useState<ReviewOpinionFormData>({
|
||||
...EMPTY_FORM_DATA,
|
||||
opnrSeComCd: editableReviewStep ?? "",
|
||||
});
|
||||
const [uploadedFile, setUploadedFile] = useState<UploadedOpinionFile | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
opnrSeComCd: editableReviewStep ?? "",
|
||||
}));
|
||||
}, [editableReviewStep]);
|
||||
|
||||
const { uploadFileAsync, isUploading, uploads } = useTus({
|
||||
onSuccess: (atflCd, atflId, _file, fileKey) => {
|
||||
if (!atflCd || !atflId) return;
|
||||
|
||||
setFormData((prev) => ({ ...prev, atchFileGroupId: atflCd }));
|
||||
setUploadedFile((prev) => (prev?.fileKey === fileKey ? { ...prev, atflCd, atflId } : prev));
|
||||
},
|
||||
onError: (_error, _file, fileKey) => {
|
||||
setUploadedFile((prev) => (prev?.fileKey === fileKey ? null : prev));
|
||||
},
|
||||
});
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
getFormData: () => {
|
||||
if (!canWriteOpinion || !editableReviewStep || !hasFormContent(formData)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
formData: {
|
||||
...formData,
|
||||
opnrSeComCd: editableReviewStep,
|
||||
},
|
||||
rvwList,
|
||||
};
|
||||
},
|
||||
resetForm: () => {
|
||||
setFormData({
|
||||
...EMPTY_FORM_DATA,
|
||||
opnrSeComCd: editableReviewStep ?? "",
|
||||
});
|
||||
setUploadedFile(null);
|
||||
},
|
||||
}),
|
||||
[canWriteOpinion, editableReviewStep, formData, rvwList],
|
||||
);
|
||||
|
||||
const kecoRvwList = rvwList.filter((item) => item.opnrSeComCd === "KECO");
|
||||
const hasKecoSection =
|
||||
kecoRvwList.length > 0 || (editableReviewStep === "KECO" && canWriteOpinion);
|
||||
|
||||
if (!hasKecoSection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
async function handleFileUpload(files: FileList | null) {
|
||||
if (!files || files.length === 0) return;
|
||||
|
||||
const file = files[0];
|
||||
const fileKey = createRvwOpnnUploadKey(file);
|
||||
const existingAtflCd = formData.atchFileGroupId;
|
||||
|
||||
setUploadedFile({ fileName: file.name, fileKey });
|
||||
|
||||
try {
|
||||
await uploadFileAsync(file, {
|
||||
fileKey,
|
||||
metadata: existingAtflCd ? { atflCd: existingAtflCd } : undefined,
|
||||
});
|
||||
} catch {
|
||||
// onError callback handles cleanup
|
||||
}
|
||||
}
|
||||
|
||||
async function handleFileRemove() {
|
||||
if (!uploadedFile) return;
|
||||
|
||||
uploads[uploadedFile.fileKey]?.abort();
|
||||
|
||||
if (uploadedFile.atflId) {
|
||||
try {
|
||||
await attachService.deleteAttachmentByAtflId(uploadedFile.atflId);
|
||||
} catch (error) {
|
||||
console.error("검토의견 첨부파일 삭제 실패:", error);
|
||||
}
|
||||
}
|
||||
|
||||
setUploadedFile(null);
|
||||
setFormData((prev) => ({ ...prev, atchFileGroupId: "" }));
|
||||
}
|
||||
|
||||
function renderOpinionForm(reviewStep: ReviewStepCode) {
|
||||
if (editableReviewStep !== reviewStep || !canWriteOpinion) return null;
|
||||
|
||||
const uploading = uploadedFile ? (isUploading[uploadedFile.fileKey] ?? false) : false;
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-xl font-semibold">검토의견 작성</h4>
|
||||
<Table caption="검토의견 작성으로 의견내용, 검토결과, 첨부파일 정보 제공" variant="secondary">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead scope="row" className="w-[12rem]">의견내용</TableHead>
|
||||
<TableCell>
|
||||
<Textarea
|
||||
value={formData.rvwRsltCn}
|
||||
onChange={(event) =>
|
||||
setFormData((prev) => ({ ...prev, rvwRsltCn: event.target.value }))
|
||||
}
|
||||
placeholder="의견내용을 입력하세요"
|
||||
rows={4}
|
||||
className="min-h-[8rem] !text-xl"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead scope="row">검토결과</TableHead>
|
||||
<TableCell>
|
||||
<FieldSet>
|
||||
<RadioGroup
|
||||
value={formData.rvwRsltAnmlyYn}
|
||||
onValueChange={(value) =>
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
rvwRsltAnmlyYn: value as ReviewOpinionFormData["rvwRsltAnmlyYn"],
|
||||
}))
|
||||
}
|
||||
>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<FieldLabel
|
||||
htmlFor={`rvw-opnn-normal-${reviewStep}`}
|
||||
className="!w-auto border-none"
|
||||
>
|
||||
<Field orientation="horizontal" className="w-auto">
|
||||
<RadioGroupItem value="N" id={`rvw-opnn-normal-${reviewStep}`} />
|
||||
<span className="font-normal">정상</span>
|
||||
</Field>
|
||||
</FieldLabel>
|
||||
<FieldLabel
|
||||
htmlFor={`rvw-opnn-abnormal-${reviewStep}`}
|
||||
className="!w-auto border-none"
|
||||
>
|
||||
<Field orientation="horizontal" className="w-auto">
|
||||
<RadioGroupItem value="Y" id={`rvw-opnn-abnormal-${reviewStep}`} />
|
||||
<span className="font-normal">이상</span>
|
||||
</Field>
|
||||
</FieldLabel>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</FieldSet>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead scope="row">첨부파일</TableHead>
|
||||
<TableCell>
|
||||
{uploadedFile ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<TextShimmer className="text-xl" active={uploading}>
|
||||
{uploadedFile.fileName}
|
||||
</TextShimmer>
|
||||
<Button
|
||||
size="icon"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
void handleFileRemove();
|
||||
}}
|
||||
disabled={uploading}
|
||||
className="size-8 !bg-destructive"
|
||||
>
|
||||
<i className="diveicon di-x text-xs" aria-label="삭제" />
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<label>
|
||||
<input
|
||||
type="file"
|
||||
className="hidden"
|
||||
onChange={(event) => {
|
||||
void handleFileUpload(event.target.files);
|
||||
event.target.value = "";
|
||||
}}
|
||||
/>
|
||||
<Button size="sm" variant="outline" type="button" asChild>
|
||||
<span>파일 선택</span>
|
||||
</Button>
|
||||
</label>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SectionCollapsible title={`${REVIEW_STEP_LABELS.KECO} 검토의견`}>
|
||||
<div className="space-y-6">
|
||||
{kecoRvwList.length > 0 ? <TechReviewOpinionTable items={kecoRvwList} /> : null}
|
||||
{renderOpinionForm("KECO")}
|
||||
</div>
|
||||
</SectionCollapsible>
|
||||
);
|
||||
});
|
||||
|
||||
function TechReviewOpinionTable({ items }: TechReviewOpinionTableProps) {
|
||||
return (
|
||||
<div className="overflow-x-auto">
|
||||
<Table caption="검토의견 목록으로 의견자명, 의견내용, 검토결과, 첨부파일, 등록일시 정보 제공" variant="secondary">
|
||||
<colgroup>
|
||||
<col width="10%" />
|
||||
<col width="55%" />
|
||||
<col width="5%" />
|
||||
<col width="20%" />
|
||||
<col width="10%" />
|
||||
</colgroup>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>의견자명</TableHead>
|
||||
<TableHead>의견내용</TableHead>
|
||||
<TableHead>검토결과</TableHead>
|
||||
<TableHead>첨부파일</TableHead>
|
||||
<TableHead>등록일시</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{items.map((item) => (
|
||||
<TableRow key={`${item.designCnstnDocNo}_${item.rvwOpnnSn}`}>
|
||||
<TableCell align="center">{item.opnrNm}</TableCell>
|
||||
<TableCell align="left">{item.rvwRsltCn || "-"}</TableCell>
|
||||
<TableCell align="center">
|
||||
{item.rvwRsltAnmlyYn === "Y" ? "이상" : item.rvwRsltAnmlyYn === "N" ? "정상" : "-"}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
{item.atchFileInfo?.atflCd ? (
|
||||
<AttachmentDownloadLink
|
||||
atflCd={item.atchFileInfo.atflCd}
|
||||
fileName={item.atchFileInfo.orgnlFileNm || "다운로드"}
|
||||
/>
|
||||
) : (
|
||||
"-"
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
{item.regDt ? dayjs(item.regDt).format("YYYY-MM-DD HH:mm:ss") : "-"}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { TechReviewOpinionSection };
|
||||
"use client";
|
||||
|
||||
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
|
||||
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { getDefaultFileKey, useTus, useUserStore } from "@/shared/lib";
|
||||
import { TextShimmer } from "@/shared/ui";
|
||||
import { SectionCollapsible } from "@/shared/ui";
|
||||
import { Field, FieldLabel, FieldSet } from "@/shared/ui/field";
|
||||
import { attachService } from "@/entities/attach";
|
||||
import {
|
||||
designCnstnKeys,
|
||||
type DesignCnstnRvwOpnnResponseDto,
|
||||
designCnstnRvwOpnnService,
|
||||
} from "@/entities/design-cnstn";
|
||||
import { RvwOpnnListTable } from "@/widgets/pbadms-sprt";
|
||||
import {
|
||||
Button,
|
||||
RadioGroup,
|
||||
RadioGroupItem,
|
||||
Table,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
Textarea,
|
||||
} from "@common/components";
|
||||
|
||||
import type {
|
||||
ReviewOpinionFormData,
|
||||
ReviewOpinionGetFormDataResult,
|
||||
} from "../../lib/review-opinion-utils";
|
||||
import type { ReviewStepCode } from "../../model/view-types";
|
||||
import { REVIEW_STEP_LABELS } from "../../model/view-types";
|
||||
|
||||
const RVW_OPNN_FILE_KEY_PREFIX = "rvw-opnn::";
|
||||
|
||||
interface TechReviewOpinionSectionProps {
|
||||
rvwList: DesignCnstnRvwOpnnResponseDto[];
|
||||
editableReviewStep?: ReviewStepCode;
|
||||
atrzSeComCdList?: string[];
|
||||
isAtrzLoading?: boolean;
|
||||
}
|
||||
|
||||
export interface TechReviewOpinionSectionHandle {
|
||||
getFormData: () => ReviewOpinionGetFormDataResult | null;
|
||||
resetForm: () => void;
|
||||
}
|
||||
|
||||
interface TechReviewOpinionTableProps {
|
||||
items: DesignCnstnRvwOpnnResponseDto[];
|
||||
}
|
||||
|
||||
interface UploadedOpinionFile {
|
||||
fileName: string;
|
||||
fileKey: string;
|
||||
atflId?: string;
|
||||
atflCd?: string;
|
||||
}
|
||||
|
||||
const EMPTY_FORM_DATA: ReviewOpinionFormData = {
|
||||
rvwRsltCn: "",
|
||||
rvwRsltAnmlyYn: "",
|
||||
atchFileGroupId: "",
|
||||
opnrSeComCd: "",
|
||||
};
|
||||
|
||||
function createRvwOpnnUploadKey(file: File) {
|
||||
return `${RVW_OPNN_FILE_KEY_PREFIX}${getDefaultFileKey(file)}`;
|
||||
}
|
||||
|
||||
function hasFormContent(formData: ReviewOpinionFormData) {
|
||||
return Boolean(formData.rvwRsltCn.trim() || formData.rvwRsltAnmlyYn || formData.atchFileGroupId);
|
||||
}
|
||||
|
||||
const TechReviewOpinionSection = forwardRef<
|
||||
TechReviewOpinionSectionHandle,
|
||||
TechReviewOpinionSectionProps
|
||||
>(function TechReviewOpinionSection(
|
||||
{ rvwList, editableReviewStep, atrzSeComCdList = [], isAtrzLoading = false },
|
||||
ref,
|
||||
) {
|
||||
const canWriteOpinion = !isAtrzLoading && atrzSeComCdList.includes("ATRZ02");
|
||||
const [formData, setFormData] = useState<ReviewOpinionFormData>({
|
||||
...EMPTY_FORM_DATA,
|
||||
opnrSeComCd: editableReviewStep ?? "",
|
||||
});
|
||||
const [uploadedFile, setUploadedFile] = useState<UploadedOpinionFile | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
opnrSeComCd: editableReviewStep ?? "",
|
||||
}));
|
||||
}, [editableReviewStep]);
|
||||
|
||||
const { uploadFileAsync, isUploading, uploads } = useTus({
|
||||
onSuccess: (atflCd, atflId, _file, fileKey) => {
|
||||
if (!atflCd || !atflId) return;
|
||||
|
||||
setFormData((prev) => ({ ...prev, atchFileGroupId: atflCd }));
|
||||
setUploadedFile((prev) => (prev?.fileKey === fileKey ? { ...prev, atflCd, atflId } : prev));
|
||||
},
|
||||
onError: (_error, _file, fileKey) => {
|
||||
setUploadedFile((prev) => (prev?.fileKey === fileKey ? null : prev));
|
||||
},
|
||||
});
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
getFormData: () => {
|
||||
if (!canWriteOpinion || !editableReviewStep || !hasFormContent(formData)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
formData: {
|
||||
...formData,
|
||||
opnrSeComCd: editableReviewStep,
|
||||
},
|
||||
rvwList,
|
||||
};
|
||||
},
|
||||
resetForm: () => {
|
||||
setFormData({
|
||||
...EMPTY_FORM_DATA,
|
||||
opnrSeComCd: editableReviewStep ?? "",
|
||||
});
|
||||
setUploadedFile(null);
|
||||
},
|
||||
}),
|
||||
[canWriteOpinion, editableReviewStep, formData, rvwList],
|
||||
);
|
||||
|
||||
const kecoRvwList = rvwList.filter((item) => item.opnrSeComCd === "KECO");
|
||||
const hasKecoSection =
|
||||
kecoRvwList.length > 0 || (editableReviewStep === "KECO" && canWriteOpinion);
|
||||
|
||||
if (!hasKecoSection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
async function handleFileUpload(files: FileList | null) {
|
||||
if (!files || files.length === 0) return;
|
||||
|
||||
const file = files[0];
|
||||
const fileKey = createRvwOpnnUploadKey(file);
|
||||
const existingAtflCd = formData.atchFileGroupId;
|
||||
|
||||
setUploadedFile({ fileName: file.name, fileKey });
|
||||
|
||||
try {
|
||||
await uploadFileAsync(file, {
|
||||
fileKey,
|
||||
metadata: existingAtflCd ? { atflCd: existingAtflCd } : undefined,
|
||||
});
|
||||
} catch {
|
||||
// onError callback handles cleanup
|
||||
}
|
||||
}
|
||||
|
||||
async function handleFileRemove() {
|
||||
if (!uploadedFile) return;
|
||||
|
||||
uploads[uploadedFile.fileKey]?.abort();
|
||||
|
||||
if (uploadedFile.atflId) {
|
||||
try {
|
||||
await attachService.deleteAttachmentByAtflId(uploadedFile.atflId);
|
||||
} catch (error) {
|
||||
console.error("검토의견 첨부파일 삭제 실패:", error);
|
||||
}
|
||||
}
|
||||
|
||||
setUploadedFile(null);
|
||||
setFormData((prev) => ({ ...prev, atchFileGroupId: "" }));
|
||||
}
|
||||
|
||||
function isReviewTurn(reviewStep: ReviewStepCode) {
|
||||
return editableReviewStep === reviewStep && canWriteOpinion;
|
||||
}
|
||||
|
||||
function renderOpinionForm(reviewStep: ReviewStepCode) {
|
||||
if (!isReviewTurn(reviewStep)) return null;
|
||||
|
||||
const uploading = uploadedFile ? (isUploading[uploadedFile.fileKey] ?? false) : false;
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-xl font-semibold">검토의견 작성</h4>
|
||||
<Table
|
||||
caption="검토의견 작성으로 의견내용, 검토결과, 첨부파일 정보 제공"
|
||||
variant="secondary"
|
||||
>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead scope="row" className="w-[12rem]">
|
||||
의견내용
|
||||
</TableHead>
|
||||
<TableCell>
|
||||
<Textarea
|
||||
value={formData.rvwRsltCn}
|
||||
onChange={(event) =>
|
||||
setFormData((prev) => ({ ...prev, rvwRsltCn: event.target.value }))
|
||||
}
|
||||
placeholder="의견내용을 입력하세요"
|
||||
rows={4}
|
||||
className="min-h-[8rem] !text-xl"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead scope="row">검토결과</TableHead>
|
||||
<TableCell>
|
||||
<FieldSet>
|
||||
<RadioGroup
|
||||
value={formData.rvwRsltAnmlyYn}
|
||||
onValueChange={(value) =>
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
rvwRsltAnmlyYn: value as ReviewOpinionFormData["rvwRsltAnmlyYn"],
|
||||
}))
|
||||
}
|
||||
>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<FieldLabel
|
||||
htmlFor={`rvw-opnn-normal-${reviewStep}`}
|
||||
className="!w-auto border-none"
|
||||
>
|
||||
<Field orientation="horizontal" className="w-auto">
|
||||
<RadioGroupItem value="N" id={`rvw-opnn-normal-${reviewStep}`} />
|
||||
<span className="font-normal">정상</span>
|
||||
</Field>
|
||||
</FieldLabel>
|
||||
<FieldLabel
|
||||
htmlFor={`rvw-opnn-abnormal-${reviewStep}`}
|
||||
className="!w-auto border-none"
|
||||
>
|
||||
<Field orientation="horizontal" className="w-auto">
|
||||
<RadioGroupItem value="Y" id={`rvw-opnn-abnormal-${reviewStep}`} />
|
||||
<span className="font-normal">이상</span>
|
||||
</Field>
|
||||
</FieldLabel>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</FieldSet>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead scope="row">첨부파일</TableHead>
|
||||
<TableCell>
|
||||
{uploadedFile ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<TextShimmer className="text-xl" active={uploading}>
|
||||
{uploadedFile.fileName}
|
||||
</TextShimmer>
|
||||
<Button
|
||||
size="icon"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
void handleFileRemove();
|
||||
}}
|
||||
disabled={uploading}
|
||||
className="size-8 !bg-destructive"
|
||||
>
|
||||
<i className="diveicon di-x text-xs" aria-label="삭제" />
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<label>
|
||||
<input
|
||||
type="file"
|
||||
className="hidden"
|
||||
onChange={(event) => {
|
||||
void handleFileUpload(event.target.files);
|
||||
event.target.value = "";
|
||||
}}
|
||||
/>
|
||||
<Button size="sm" variant="outline" type="button" asChild>
|
||||
<span>파일 선택</span>
|
||||
</Button>
|
||||
</label>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SectionCollapsible title={`${REVIEW_STEP_LABELS.KECO} 검토의견`}>
|
||||
<div className="space-y-6">
|
||||
{kecoRvwList.length > 0 ? (
|
||||
<TechReviewOpinionTable items={kecoRvwList} />
|
||||
) : null}
|
||||
{renderOpinionForm("KECO")}
|
||||
</div>
|
||||
</SectionCollapsible>
|
||||
);
|
||||
});
|
||||
|
||||
function getRvwOpnnIds(item: DesignCnstnRvwOpnnResponseDto) {
|
||||
if (!item.designCnstnDocNo || item.rvwOpnnSn == null) {
|
||||
throw new Error("검토의견 식별값이 없습니다.");
|
||||
}
|
||||
return [item.designCnstnDocNo, item.rvwOpnnSn] as const;
|
||||
}
|
||||
|
||||
function TechReviewOpinionTable({ items }: TechReviewOpinionTableProps) {
|
||||
const queryClient = useQueryClient();
|
||||
const currentUserUuid = useUserStore((state) => state.userInfo?.userUnqId);
|
||||
|
||||
return (
|
||||
<RvwOpnnListTable
|
||||
items={items}
|
||||
getRowKey={(item) => `${item.designCnstnDocNo}_${item.rvwOpnnSn}`}
|
||||
currentUserUuid={currentUserUuid}
|
||||
onUpdate={async (item, body) => {
|
||||
await designCnstnRvwOpnnService.update(...getRvwOpnnIds(item), body);
|
||||
await queryClient.invalidateQueries({ queryKey: designCnstnKeys.all });
|
||||
}}
|
||||
onDelete={async (item) => {
|
||||
await designCnstnRvwOpnnService.delete(...getRvwOpnnIds(item));
|
||||
await queryClient.invalidateQueries({ queryKey: designCnstnKeys.all });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export { TechReviewOpinionSection };
|
||||
|
||||
@@ -51,6 +51,19 @@ export function createEsntlAtchFileColumns({
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => formatYn(getValue()),
|
||||
}),
|
||||
columnHelper.accessor("esntlAtchFileRvwCn", {
|
||||
header: "필수파일검토내용",
|
||||
meta: { align: "left" },
|
||||
cell: ({ getValue }) => {
|
||||
const val = getValue();
|
||||
if (!val?.trim()) return "-";
|
||||
return (
|
||||
<span className="line-clamp-2 max-w-[20rem] text-left" title={val}>
|
||||
{val}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor("esntlAtchFileRmrk", {
|
||||
header: "비고",
|
||||
meta: { align: "center" },
|
||||
|
||||
@@ -26,6 +26,7 @@ export interface EsntlAtchFileFormValues {
|
||||
docSeComCd: string;
|
||||
esntlAtchFileNm: string;
|
||||
esntlAtchYn: "Y" | "N";
|
||||
esntlAtchFileRvwCn: string;
|
||||
esntlAtchFileRmrk: string;
|
||||
atchFileGroupId: string;
|
||||
}
|
||||
@@ -68,6 +69,7 @@ function EsntlAtchFileFormDialog({
|
||||
docSeComCd: "",
|
||||
esntlAtchFileNm: "",
|
||||
esntlAtchYn: "Y",
|
||||
esntlAtchFileRvwCn: "",
|
||||
esntlAtchFileRmrk: "",
|
||||
atchFileGroupId: "",
|
||||
},
|
||||
@@ -115,6 +117,7 @@ function EsntlAtchFileFormDialog({
|
||||
docSeComCd: initial.docSeComCd ?? "",
|
||||
esntlAtchFileNm: initial.esntlAtchFileNm ?? "",
|
||||
esntlAtchYn: initial.esntlAtchYn ?? "Y",
|
||||
esntlAtchFileRvwCn: initial.esntlAtchFileRvwCn ?? "",
|
||||
esntlAtchFileRmrk: initial.esntlAtchFileRmrk ?? "",
|
||||
atchFileGroupId: initial.atchFileGroupId ?? "",
|
||||
});
|
||||
@@ -127,6 +130,7 @@ function EsntlAtchFileFormDialog({
|
||||
docSeComCd: "",
|
||||
esntlAtchFileNm: "",
|
||||
esntlAtchYn: "Y",
|
||||
esntlAtchFileRvwCn: "",
|
||||
esntlAtchFileRmrk: "",
|
||||
atchFileGroupId: "",
|
||||
});
|
||||
@@ -249,6 +253,17 @@ function EsntlAtchFileFormDialog({
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="esntlAtchFileRvwCn">필수파일검토내용</Label>
|
||||
<Textarea
|
||||
id="esntlAtchFileRvwCn"
|
||||
rows={3}
|
||||
placeholder="검토내용을 입력하세요"
|
||||
maxLength={4000}
|
||||
{...register("esntlAtchFileRvwCn")}
|
||||
className="!text-xl"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="esntlAtchFileRmrk">비고</Label>
|
||||
<Textarea
|
||||
|
||||
@@ -100,7 +100,7 @@ function EsntlAtchFileList({
|
||||
manualPagination: true,
|
||||
},
|
||||
dataTableOptions: {
|
||||
caption: "필수첨부파일 목록으로 필수첨부파일아이디, 문서구분, 필수첨부파일명, 필수첨부여부, 비고, 양식 내려받기, 관리 정보 제공",
|
||||
caption: "필수첨부파일 목록으로 필수첨부파일아이디, 문서구분, 필수첨부파일명, 필수첨부여부, 필수파일검토내용, 비고, 양식 내려받기, 관리 정보 제공",
|
||||
viewPagination: true,
|
||||
},
|
||||
});
|
||||
@@ -112,6 +112,7 @@ function EsntlAtchFileList({
|
||||
docSeComCd: values.docSeComCd,
|
||||
esntlAtchFileNm: values.esntlAtchFileNm,
|
||||
esntlAtchYn: values.esntlAtchYn,
|
||||
esntlAtchFileRvwCn: values.esntlAtchFileRvwCn || undefined,
|
||||
esntlAtchFileRmrk: values.esntlAtchFileRmrk || undefined,
|
||||
atchFileGroupId: values.atchFileGroupId || undefined,
|
||||
};
|
||||
@@ -136,14 +137,14 @@ function EsntlAtchFileList({
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="card card-border mx-0 bg-background pt-0">
|
||||
<div className="card card-border pt-0">
|
||||
<div className="card-header flex items-center justify-between">
|
||||
<h3 className="text-2xl font-semibold">필수첨부파일 목록</h3>
|
||||
<h3 className="text-2xl font-medium text-label">필수첨부파일 목록</h3>
|
||||
<Button type="button" color="green" onClick={handleOpenCreate}>
|
||||
등록
|
||||
</Button>
|
||||
</div>
|
||||
{tableComponent}
|
||||
<div className="card !m-[1.2rem] !p-0">{tableComponent}</div>
|
||||
{dialogMode ? (
|
||||
<EsntlAtchFileFormDialog
|
||||
mode={dialogMode}
|
||||
@@ -159,7 +160,7 @@ function EsntlAtchFileList({
|
||||
onSubmit={handleFormSubmit}
|
||||
/>
|
||||
) : null}
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -52,8 +52,8 @@ function EsntlAtchFileMngPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6 px-[1.8rem] pb-6">
|
||||
<section className="card card-border mx-0 bg-background">
|
||||
<div className="card card-border !mt-6">
|
||||
<div className="card card-border">
|
||||
<form className="flex flex-wrap items-end gap-4 p-4" onSubmit={handleSearch}>
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<Label htmlFor="docSeComCd">문서구분</Label>
|
||||
@@ -79,7 +79,7 @@ function EsntlAtchFileMngPage() {
|
||||
<Button type="submit">조회</Button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<EsntlAtchFileList
|
||||
searchParams={searchParams}
|
||||
|
||||
@@ -1,216 +1,230 @@
|
||||
"use client";
|
||||
|
||||
import { Fragment } from "react";
|
||||
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
|
||||
import { dayjs } from "@/shared/lib";
|
||||
import type { FnrsCnsltnMngResponseDto } from "@/entities/fnrs-cnsltn";
|
||||
import { Badge } from "@common/components";
|
||||
|
||||
const columnHelper = createColumnHelper<FnrsCnsltnMngResponseDto>();
|
||||
|
||||
const MAIN_STEP_LABELS = {
|
||||
apply: "재원협의 신청",
|
||||
envoReview: "재원협의 검토",
|
||||
techReview: "기술검토",
|
||||
reply: "검토결과 회신",
|
||||
} as const;
|
||||
|
||||
export const PROGRESS_STATUS_COLUMNS = [
|
||||
{ label: "재원협의 신청 (시군)", sttsComCd: "ST" },
|
||||
{ label: "재원협의 신청 (시도)", sttsComCd: "LE" },
|
||||
{ label: "재원협의 검토", sttsComCd: "ER" },
|
||||
{ label: "기술검토", sttsComCd: "TE" },
|
||||
{ label: "기술검토 회신", sttsComCd: "TP" },
|
||||
{ label: "환경청 검토 결과 회신", sttsComCd: "EP" },
|
||||
];
|
||||
|
||||
type BadgeState = "completed" | "current" | "pending";
|
||||
|
||||
interface ProgressBadge {
|
||||
label: string;
|
||||
state: BadgeState;
|
||||
}
|
||||
|
||||
function buildProgressBadges(prgrsSttsComCd: string, techRplyHstryYn: "Y" | "N"): ProgressBadge[] {
|
||||
if (prgrsSttsComCd === "SV") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
if (prgrsSttsComCd === "ST") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "current" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
if (prgrsSttsComCd === "LE") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "current" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
if (["FV", "ER", "AC", "TE", "RB", "RA", "AV", "EV", "RK", "RR", "SL"].includes(prgrsSttsComCd)) {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "current" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
if (prgrsSttsComCd === "TP") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "completed" },
|
||||
{
|
||||
label: MAIN_STEP_LABELS.techReview,
|
||||
state: techRplyHstryYn === "Y" ? "completed" : "pending",
|
||||
},
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "current" },
|
||||
];
|
||||
}
|
||||
|
||||
if (prgrsSttsComCd === "EP" || prgrsSttsComCd === "TA") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "completed" },
|
||||
{
|
||||
label: MAIN_STEP_LABELS.techReview,
|
||||
state: techRplyHstryYn === "Y" ? "completed" : "pending",
|
||||
},
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "completed" },
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
function getProgressStatusBadge(prgrsSttsComCd?: string | null) {
|
||||
if (prgrsSttsComCd === "SL") {
|
||||
return <Badge color="orange">보완요청</Badge>;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getBadgeColor(state: BadgeState) {
|
||||
switch (state) {
|
||||
case "completed":
|
||||
return "green";
|
||||
case "current":
|
||||
return "blue";
|
||||
case "pending":
|
||||
return "lightGray";
|
||||
}
|
||||
}
|
||||
|
||||
function getBadgeStateText(state: BadgeState) {
|
||||
switch (state) {
|
||||
case "completed":
|
||||
return "완료";
|
||||
case "current":
|
||||
return "진행중";
|
||||
case "pending":
|
||||
return "대기";
|
||||
}
|
||||
}
|
||||
|
||||
function formatOptional(value: string | null | undefined) {
|
||||
return value?.trim() ? value : "-";
|
||||
}
|
||||
|
||||
interface ProgressStepBadgeProps {
|
||||
prgrsSttsComCd: string;
|
||||
techRplyHstryYn: "Y" | "N";
|
||||
}
|
||||
|
||||
function ProgressStepBadge({ prgrsSttsComCd, techRplyHstryYn }: ProgressStepBadgeProps) {
|
||||
const badges = buildProgressBadges(prgrsSttsComCd, techRplyHstryYn);
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-center justify-start">
|
||||
{badges.map((badge, index) => (
|
||||
<Fragment key={badge.label}>
|
||||
{index > 0 && <span className="mx-2">></span>}
|
||||
<Badge color={getBadgeColor(badge.state)}>
|
||||
{badge.label}({getBadgeStateText(badge.state)})
|
||||
</Badge>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function createFnrsCnsltnColumns() {
|
||||
return [
|
||||
columnHelper.accessor("sido", {
|
||||
header: "시도",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
}),
|
||||
columnHelper.accessor("gugun", {
|
||||
header: "시군구",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
}),
|
||||
columnHelper.accessor("bizNm", {
|
||||
header: "사업명",
|
||||
meta: { align: "left" },
|
||||
cell: ({ row, getValue }) => (
|
||||
<>
|
||||
{getProgressStatusBadge(row.original.prgrsSttsComCd)} {formatOptional(getValue())}
|
||||
</>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("taskSeCdNm", {
|
||||
header: "업무구분",
|
||||
meta: { align: "center" },
|
||||
cell: ({ row }) => formatOptional(row.original.taskSeCdNm || row.original.taskSeComCd),
|
||||
}),
|
||||
columnHelper.accessor("aplcntNm", {
|
||||
header: "담당자",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
}),
|
||||
columnHelper.accessor("envoSeCdNm", {
|
||||
header: "해당기관",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: "progress",
|
||||
header: "진행상황",
|
||||
meta: { align: "center" },
|
||||
cell: ({ row }) => (
|
||||
<ProgressStepBadge
|
||||
prgrsSttsComCd={row.original.prgrsSttsComCd || ""}
|
||||
techRplyHstryYn={row.original.techRplyHstryYn || "N"}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("regDt", {
|
||||
header: "신청일",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => {
|
||||
const value = getValue();
|
||||
return value ? dayjs(value).format("YYYY-MM-DD") : "-";
|
||||
},
|
||||
}),
|
||||
];
|
||||
}
|
||||
"use client";
|
||||
|
||||
import { Fragment } from "react";
|
||||
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
|
||||
import { dayjs } from "@/shared/lib";
|
||||
import type { FnrsCnsltnMngResponseDto } from "@/entities/fnrs-cnsltn";
|
||||
import { Badge } from "@common/components";
|
||||
|
||||
const columnHelper = createColumnHelper<FnrsCnsltnMngResponseDto>();
|
||||
|
||||
const MAIN_STEP_LABELS = {
|
||||
apply: "재원협의 신청",
|
||||
envoReview: "재원협의 검토",
|
||||
techReview: "기술검토",
|
||||
reply: "검토결과 회신",
|
||||
} as const;
|
||||
|
||||
export const PROGRESS_STATUS_COLUMNS = [
|
||||
{ label: "재원협의 신청 (시군)", sttsComCd: "ST" },
|
||||
{ label: "재원협의 신청 (시도)", sttsComCd: "LE" },
|
||||
{ label: "재원협의 검토", sttsComCd: "ER" },
|
||||
{ label: "기술검토", sttsComCd: "TE" },
|
||||
{ label: "기술검토 회신", sttsComCd: "TP" },
|
||||
{ label: "환경청 검토 결과 회신", sttsComCd: "EP" },
|
||||
];
|
||||
|
||||
type BadgeState = "completed" | "current" | "pending";
|
||||
|
||||
interface ProgressBadge {
|
||||
label: string;
|
||||
state: BadgeState;
|
||||
}
|
||||
|
||||
function buildProgressBadges(prgrsSttsComCd: string, techRplyHstryYn: "Y" | "N"): ProgressBadge[] {
|
||||
if (prgrsSttsComCd === "SV") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
if (prgrsSttsComCd === "ST") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "current" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
if (prgrsSttsComCd === "LE") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "current" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
if (["FV", "ER", "AC", "TE", "RB", "RA", "AV", "EV", "RK", "RR", "SL"].includes(prgrsSttsComCd)) {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "current" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
if (prgrsSttsComCd === "TP") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "completed" },
|
||||
{
|
||||
label: MAIN_STEP_LABELS.techReview,
|
||||
state: techRplyHstryYn === "Y" ? "completed" : "pending",
|
||||
},
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "current" },
|
||||
];
|
||||
}
|
||||
|
||||
if (prgrsSttsComCd === "EP" || prgrsSttsComCd === "TA") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "completed" },
|
||||
{
|
||||
label: MAIN_STEP_LABELS.techReview,
|
||||
state: techRplyHstryYn === "Y" ? "completed" : "pending",
|
||||
},
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "completed" },
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
function getProgressStatusBadge(prgrsSttsComCd?: string | null) {
|
||||
if (prgrsSttsComCd === "SL") {
|
||||
return <Badge color="orange">보완요청</Badge>;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getBadgeColor(state: BadgeState) {
|
||||
switch (state) {
|
||||
case "completed":
|
||||
return "green";
|
||||
case "current":
|
||||
return "blue";
|
||||
case "pending":
|
||||
return "lightGray";
|
||||
}
|
||||
}
|
||||
|
||||
function getBadgeStateText(state: BadgeState) {
|
||||
switch (state) {
|
||||
case "completed":
|
||||
return "완료";
|
||||
case "current":
|
||||
return "진행중";
|
||||
case "pending":
|
||||
return "대기";
|
||||
}
|
||||
}
|
||||
|
||||
/** 단계명을 2줄로 분리: "설치인가 신청" → ["설치인가", "신청"], "기술검토" → ["기술검토", ""] */
|
||||
function splitStepLabel(label: string) {
|
||||
const [title = "", ...rest] = label.split(" ");
|
||||
return { title, detail: rest.join(" ") };
|
||||
}
|
||||
|
||||
function formatOptional(value: string | null | undefined) {
|
||||
return value?.trim() ? value : "-";
|
||||
}
|
||||
|
||||
interface ProgressStepBadgeProps {
|
||||
prgrsSttsComCd: string;
|
||||
techRplyHstryYn: "Y" | "N";
|
||||
}
|
||||
|
||||
function ProgressStepBadge({ prgrsSttsComCd, techRplyHstryYn }: ProgressStepBadgeProps) {
|
||||
const badges = buildProgressBadges(prgrsSttsComCd, techRplyHstryYn);
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-center justify-center">
|
||||
{badges.map((badge, index) => (
|
||||
<Fragment key={badge.label}>
|
||||
{index > 0 && <span className="mx-2">></span>}
|
||||
{/* 1줄: 단계명 / 2줄: 세부단계 + 상태 (예: 설치인가 / 신청완료, 기술검토 / 완료) */}
|
||||
<Badge
|
||||
color={getBadgeColor(badge.state)}
|
||||
className="w-[100px] flex-col justify-center !px-0 py-1 text-center leading-tight"
|
||||
>
|
||||
<span>{splitStepLabel(badge.label).title}</span>
|
||||
<span>
|
||||
{splitStepLabel(badge.label).detail}
|
||||
{getBadgeStateText(badge.state)}
|
||||
</span>
|
||||
</Badge>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function createFnrsCnsltnColumns() {
|
||||
return [
|
||||
columnHelper.accessor("sido", {
|
||||
header: "시도",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
}),
|
||||
columnHelper.accessor("gugun", {
|
||||
header: "시군구",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
}),
|
||||
columnHelper.accessor("bizNm", {
|
||||
header: "사업명",
|
||||
meta: { align: "left" },
|
||||
cell: ({ row, getValue }) => (
|
||||
<>
|
||||
{getProgressStatusBadge(row.original.prgrsSttsComCd)} {formatOptional(getValue())}
|
||||
</>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("taskSeCdNm", {
|
||||
header: "업무구분",
|
||||
meta: { align: "center" },
|
||||
cell: ({ row }) => formatOptional(row.original.taskSeCdNm || row.original.taskSeComCd),
|
||||
}),
|
||||
columnHelper.accessor("aplcntNm", {
|
||||
header: "담당자",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
}),
|
||||
columnHelper.accessor("envoSeCdNm", {
|
||||
header: "해당기관",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: "progress",
|
||||
header: "진행상황",
|
||||
meta: { align: "center" },
|
||||
cell: ({ row }) => (
|
||||
<ProgressStepBadge
|
||||
prgrsSttsComCd={row.original.prgrsSttsComCd || ""}
|
||||
techRplyHstryYn={row.original.techRplyHstryYn || "N"}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("regDt", {
|
||||
header: "신청일",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => {
|
||||
const value = getValue();
|
||||
return value ? dayjs(value).format("YYYY-MM-DD") : "-";
|
||||
},
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -33,6 +33,9 @@ import {
|
||||
interface PwrkCnsltnDocEvlDialogProps {
|
||||
open: boolean;
|
||||
pwrkCnsltnDocNo: string;
|
||||
/** 평가 저장 성공 시 호출 (이후 웹기안기 문서 저장이 진행된다) */
|
||||
onSaved: () => void;
|
||||
/** 평가 없이 닫을 때 호출 (웹기안기 문서는 저장되지 않는다) */
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
@@ -41,7 +44,12 @@ const DEFAULT_VALUES: Partial<PwrkCnsltnDocEvlFormValues> = {
|
||||
pwrkCnsltnDocEvlCn: "",
|
||||
};
|
||||
|
||||
function PwrkCnsltnDocEvlDialog({ open, pwrkCnsltnDocNo, onClose }: PwrkCnsltnDocEvlDialogProps) {
|
||||
function PwrkCnsltnDocEvlDialog({
|
||||
open,
|
||||
pwrkCnsltnDocNo,
|
||||
onSaved,
|
||||
onClose,
|
||||
}: PwrkCnsltnDocEvlDialogProps) {
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
const { mutateAsync: saveEvl, isPending } = useSavePwrkCnsltnDocEvl();
|
||||
|
||||
@@ -65,8 +73,7 @@ function PwrkCnsltnDocEvlDialog({ open, pwrkCnsltnDocNo, onClose }: PwrkCnsltnDo
|
||||
try {
|
||||
const payload = buildPwrkCnsltnDocEvlSavePayload(pwrkCnsltnDocNo, values);
|
||||
await saveEvl(payload);
|
||||
alert("AI 초안 만족도 평가를 등록했습니다.");
|
||||
onClose();
|
||||
onSaved();
|
||||
} catch (error) {
|
||||
console.error("AI 초안 만족도 평가 등록 실패:", error);
|
||||
alert("AI 초안 만족도 평가 등록에 실패했습니다.");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
@@ -46,6 +46,7 @@ const HangulGiangi = dynamic(
|
||||
|
||||
const PWRK_ATCH_FILE_KEY_PREFIX = "pwrk-cnsltn-doc::";
|
||||
|
||||
/** 현재 버전이 이 값보다 작으면(AI 초안 상태) 저장 전에 만족도 평가를 먼저 받는다. */
|
||||
const AI_DRAFT_EVL_MAX_VER_SN = 2;
|
||||
|
||||
const APPROVER_ATRZ_SE_COM_CDS = new Set(["ATRZ03", "ATRZ99"]);
|
||||
@@ -88,6 +89,8 @@ function PwrkCnsltnDocMngDialog({
|
||||
const [uploadedFiles, setUploadedFiles] = useState<UploadedAtchFile[]>([]);
|
||||
const [isPjtcoAjmtTableOpen, setIsPjtcoAjmtTableOpen] = useState(false);
|
||||
const [isEvlDialogOpen, setIsEvlDialogOpen] = useState(false);
|
||||
// 평가 모달 결과(평가 저장 여부)를 저장 흐름으로 돌려주는 resolver
|
||||
const evlResolverRef = useRef<((isEvlSaved: boolean) => void) | null>(null);
|
||||
const { mutate: downloadByAtflId } = useDownloadAttachmentByAtflId();
|
||||
|
||||
const {
|
||||
@@ -145,6 +148,8 @@ function PwrkCnsltnDocMngDialog({
|
||||
setAtchFileGroupId("");
|
||||
setUploadedFiles([]);
|
||||
setIsEvlDialogOpen(false);
|
||||
evlResolverRef.current?.(false);
|
||||
evlResolverRef.current = null;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -178,9 +183,32 @@ function PwrkCnsltnDocMngDialog({
|
||||
if (!nextOpen) onClose();
|
||||
}
|
||||
|
||||
function requestEvl() {
|
||||
return new Promise<boolean>((resolve) => {
|
||||
evlResolverRef.current = resolve;
|
||||
setIsEvlDialogOpen(true);
|
||||
});
|
||||
}
|
||||
|
||||
function resolveEvl(isEvlSaved: boolean) {
|
||||
setIsEvlDialogOpen(false);
|
||||
evlResolverRef.current?.(isEvlSaved);
|
||||
evlResolverRef.current = null;
|
||||
}
|
||||
|
||||
async function handleSaveDocument(file: Blob) {
|
||||
if (!completedDraft) throw new Error("저장할 실무협의서 초안이 없습니다.");
|
||||
|
||||
// AI 초안(버전 < 2) 저장 시에는 만족도 평가를 먼저 저장해야 웹기안기 문서가 저장된다.
|
||||
const currentVerSn = completedDraft.crtRslt?.verSn ?? 0;
|
||||
if (currentVerSn < AI_DRAFT_EVL_MAX_VER_SN) {
|
||||
const isEvlSaved = await requestEvl();
|
||||
if (!isEvlSaved) {
|
||||
alert("AI 초안 만족도 평가를 등록해야 실무협의서가 저장됩니다.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const saveDto = pwrkCnsltnDocCrtRsltSaveRequestSchema.parse({
|
||||
crtDmndUuid: completedDraft.crtDmndUuid,
|
||||
pwrkCnsltnDocNo: completedDraft.pwrkCnsltnDocNo,
|
||||
@@ -201,10 +229,6 @@ function PwrkCnsltnDocMngDialog({
|
||||
}),
|
||||
]);
|
||||
alert(`실무협의서를 저장했습니다. (버전 ${savedDocument.verSn})`);
|
||||
|
||||
if (savedDocument.verSn <= AI_DRAFT_EVL_MAX_VER_SN) {
|
||||
setIsEvlDialogOpen(true);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDownload() {
|
||||
@@ -386,7 +410,8 @@ function PwrkCnsltnDocMngDialog({
|
||||
<PwrkCnsltnDocEvlDialog
|
||||
open={isEvlDialogOpen}
|
||||
pwrkCnsltnDocNo={completedDraft.pwrkCnsltnDocNo}
|
||||
onClose={() => setIsEvlDialogOpen(false)}
|
||||
onSaved={() => resolveEvl(true)}
|
||||
onClose={() => resolveEvl(false)}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
|
||||
@@ -77,8 +77,8 @@ function FnrsListPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4 px-[1.8rem] pb-6">
|
||||
<section className="card card-border mx-0 bg-background">
|
||||
<div className="card card-border !mt-6">
|
||||
<div className="card card-border">
|
||||
<form className="flex flex-wrap items-center gap-4 p-2" onSubmit={handleSearch}>
|
||||
<div className="flex items-center gap-2">
|
||||
<Label htmlFor="sidoComCd" className="whitespace-nowrap">
|
||||
@@ -145,7 +145,7 @@ function FnrsListPage() {
|
||||
</div>
|
||||
<Button type="submit">조회</Button>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<FnrsList
|
||||
searchParams={searchParams}
|
||||
|
||||
@@ -78,15 +78,15 @@ function FnrsList({ searchParams, pagination, onPaginationChange }: FnrsListProp
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className="card card-border mx-0 bg-background pb-8 pt-0">
|
||||
<div className="card card-border pt-0">
|
||||
<div className="card-header flex items-center justify-between">
|
||||
<h3 className="text-2xl font-semibold">재원협의 목록</h3>
|
||||
<h3 className="text-2xl font-medium text-label">재원협의 목록</h3>
|
||||
<Button type="button" color="green" onClick={() => setIsTaskSeDialogOpen(true)}>
|
||||
신청
|
||||
</Button>
|
||||
</div>
|
||||
{tableComponent}
|
||||
</section>
|
||||
<div className="card !m-[1.2rem] !p-0">{tableComponent}</div>
|
||||
</div>
|
||||
<PbadmsTaskApplyDialog
|
||||
open={isTaskSeDialogOpen}
|
||||
onOpenChange={setIsTaskSeDialogOpen}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { SectionCollapsible } from "@/shared/ui";
|
||||
import { Field, FieldError, FieldGroup, FieldLabel, FieldSet } from "@/shared/ui/field";
|
||||
import { findCodeLabel, useCodeList } from "@/entities/code";
|
||||
import { type BnsApplyData, BnsPickerDialog } from "@/widgets/bns-picker";
|
||||
import { PreviousPlanLookupButton } from "@/widgets/previous-plan-lookup";
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
@@ -27,9 +28,9 @@ function BasicInfoFormSection() {
|
||||
const { control, watch, setValue } = useFormContext<FnrsCnsltnRegistFormData>();
|
||||
const [isPickerOpen, setIsPickerOpen] = useState(false);
|
||||
const taskSeComCd = watch("fnrsCnsltnMng.taskSeComCd");
|
||||
const localGovLabel = [watch("fnrsCnsltnMng.sido"), watch("fnrsCnsltnMng.gugun")]
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
const sido = watch("fnrsCnsltnMng.sido");
|
||||
const gugun = watch("fnrsCnsltnMng.gugun");
|
||||
const localGovLabel = [sido, gugun].filter(Boolean).join(" ");
|
||||
const { data: taskOptions } = useCodeList("FNRS");
|
||||
const { data: rtlztnFcltOptions } = useCodeList("RTLZTNFCLTSE");
|
||||
const { data: rtlztnUsgOptions } = useCodeList("RTLZTNUSGSE");
|
||||
@@ -49,6 +50,7 @@ function BasicInfoFormSection() {
|
||||
|
||||
return (
|
||||
<SectionCollapsible title="기본 정보">
|
||||
<PreviousPlanLookupButton sidoNm={sido} gugunNm={gugun} />
|
||||
<Table caption="기본 정보 입력으로 사업명, 사업기간, 사업내용, 구분, 신청 지자체, 시설종류, 재이용용도 정보 제공" variant="secondary">
|
||||
<colgroup>
|
||||
<col width="15%" />
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { SectionCollapsible } from "@/shared/ui";
|
||||
import type { FnrsCnsltnMngResponseDto } from "@/entities/fnrs-cnsltn";
|
||||
import { PreviousPlanLookupButton } from "@/widgets/previous-plan-lookup";
|
||||
import { Table, TableCell, TableHead, TableHeader, TableRow } from "@common/components";
|
||||
|
||||
interface BasicInfoSectionProps {
|
||||
@@ -11,6 +12,7 @@ interface BasicInfoSectionProps {
|
||||
function BasicInfoSection({ det }: BasicInfoSectionProps) {
|
||||
return (
|
||||
<SectionCollapsible title="기본 정보">
|
||||
<PreviousPlanLookupButton sidoNm={det.sido} gugunNm={det.gugun} />
|
||||
<Table caption="기본 정보로 사업명, 사업기간, 사업내용, 환경청구분, 구분, 신청지자체, 확정예산사업번호, 예산신청연도 정보 제공" variant="secondary">
|
||||
<colgroup>
|
||||
<col width="15%" />
|
||||
|
||||
@@ -1,352 +1,345 @@
|
||||
"use client";
|
||||
|
||||
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
|
||||
|
||||
import { dayjs, getDefaultFileKey, useTus } from "@/shared/lib";
|
||||
import { AttachmentFileField, SectionCollapsible } from "@/shared/ui";
|
||||
import { Field, FieldLabel, FieldSet } from "@/shared/ui/field";
|
||||
import {
|
||||
AttachmentDownloadLink,
|
||||
attachService,
|
||||
useDownloadAttachmentByAtflId,
|
||||
} from "@/entities/attach";
|
||||
import type { FnrsCnsltnRvwOpnnResponseDto } from "@/entities/fnrs-cnsltn";
|
||||
import {
|
||||
RadioGroup,
|
||||
RadioGroupItem,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
Textarea,
|
||||
} from "@common/components";
|
||||
|
||||
import type {
|
||||
ReviewOpinionFormData,
|
||||
ReviewOpinionGetFormDataResult,
|
||||
} from "../../lib/review-opinion-utils";
|
||||
import type { ReviewStepCode } from "../../model/view-types";
|
||||
import { REVIEW_STEP_LABELS } from "../../model/view-types";
|
||||
|
||||
const RVW_OPNN_FILE_KEY_PREFIX = "rvw-opnn::";
|
||||
|
||||
interface TechReviewOpinionSectionProps {
|
||||
rvwList: FnrsCnsltnRvwOpnnResponseDto[];
|
||||
editableReviewStep?: ReviewStepCode;
|
||||
atrzSeComCdList?: string[];
|
||||
isAtrzLoading?: boolean;
|
||||
}
|
||||
|
||||
export interface TechReviewOpinionSectionHandle {
|
||||
getFormData: () => ReviewOpinionGetFormDataResult | null;
|
||||
resetForm: () => void;
|
||||
}
|
||||
|
||||
interface TechReviewOpinionTableProps {
|
||||
items: FnrsCnsltnRvwOpnnResponseDto[];
|
||||
}
|
||||
|
||||
interface UploadedOpinionFile {
|
||||
fileName: string;
|
||||
fileKey: string;
|
||||
atflId?: string;
|
||||
atflCd?: string;
|
||||
}
|
||||
|
||||
const EMPTY_FORM_DATA: ReviewOpinionFormData = {
|
||||
rvwRsltCn: "",
|
||||
rvwRsltAnmlyYn: "",
|
||||
atchFileGroupId: "",
|
||||
opnrSeComCd: "",
|
||||
};
|
||||
|
||||
function createRvwOpnnUploadKey(file: File) {
|
||||
return `${RVW_OPNN_FILE_KEY_PREFIX}${getDefaultFileKey(file)}`;
|
||||
}
|
||||
|
||||
function hasFormContent(formData: ReviewOpinionFormData) {
|
||||
return Boolean(formData.rvwRsltCn.trim() || formData.rvwRsltAnmlyYn || formData.atchFileGroupId);
|
||||
}
|
||||
|
||||
const TechReviewOpinionSection = forwardRef<
|
||||
TechReviewOpinionSectionHandle,
|
||||
TechReviewOpinionSectionProps
|
||||
>(function TechReviewOpinionSection(
|
||||
{ rvwList, editableReviewStep, atrzSeComCdList = [], isAtrzLoading = false },
|
||||
ref,
|
||||
) {
|
||||
const canWriteKecoOpinion = !isAtrzLoading && atrzSeComCdList.includes("ATRZ02");
|
||||
const [formData, setFormData] = useState<ReviewOpinionFormData>({
|
||||
...EMPTY_FORM_DATA,
|
||||
opnrSeComCd: editableReviewStep ?? "",
|
||||
});
|
||||
const [uploadedFile, setUploadedFile] = useState<UploadedOpinionFile | null>(null);
|
||||
const { mutate: downloadByAtflId } = useDownloadAttachmentByAtflId();
|
||||
|
||||
useEffect(() => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
opnrSeComCd: editableReviewStep ?? "",
|
||||
}));
|
||||
}, [editableReviewStep]);
|
||||
|
||||
const { uploadFileAsync, isUploading, uploads } = useTus({
|
||||
onSuccess: (atflCd, atflId, _file, fileKey) => {
|
||||
if (!atflCd || !atflId) return;
|
||||
|
||||
setFormData((prev) => ({ ...prev, atchFileGroupId: atflCd }));
|
||||
setUploadedFile((prev) => (prev?.fileKey === fileKey ? { ...prev, atflCd, atflId } : prev));
|
||||
},
|
||||
onError: (_error, _file, fileKey) => {
|
||||
setUploadedFile((prev) => (prev?.fileKey === fileKey ? null : prev));
|
||||
},
|
||||
});
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
getFormData: () => {
|
||||
if (!editableReviewStep || !hasFormContent(formData)) {
|
||||
return null;
|
||||
}
|
||||
if (editableReviewStep === "KECO" && !canWriteKecoOpinion) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
formData: {
|
||||
...formData,
|
||||
opnrSeComCd: editableReviewStep,
|
||||
},
|
||||
rvwList,
|
||||
};
|
||||
},
|
||||
resetForm: () => {
|
||||
setFormData({
|
||||
...EMPTY_FORM_DATA,
|
||||
opnrSeComCd: editableReviewStep ?? "",
|
||||
});
|
||||
setUploadedFile(null);
|
||||
},
|
||||
}),
|
||||
[canWriteKecoOpinion, editableReviewStep, formData, rvwList],
|
||||
);
|
||||
|
||||
const envRvwList = rvwList.filter((item) => item.opnrSeComCd === "ENV");
|
||||
const kecoRvwList = rvwList.filter((item) => item.opnrSeComCd === "KECO");
|
||||
|
||||
const hasEnvSection = envRvwList.length > 0 || editableReviewStep === "ENV";
|
||||
const hasKecoSection = kecoRvwList.length > 0 || editableReviewStep === "KECO";
|
||||
|
||||
if (!hasEnvSection && !hasKecoSection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
async function handleFileUpload(files: FileList | null) {
|
||||
if (!files || files.length === 0) return;
|
||||
|
||||
const file = files[0];
|
||||
const fileKey = createRvwOpnnUploadKey(file);
|
||||
const existingAtflCd = formData.atchFileGroupId;
|
||||
|
||||
setUploadedFile({ fileName: file.name, fileKey });
|
||||
|
||||
try {
|
||||
await uploadFileAsync(file, {
|
||||
fileKey,
|
||||
metadata: existingAtflCd ? { atflCd: existingAtflCd } : undefined,
|
||||
});
|
||||
} catch {
|
||||
// onError callback handles cleanup
|
||||
}
|
||||
}
|
||||
|
||||
async function handleFileRemove() {
|
||||
if (!uploadedFile) return;
|
||||
|
||||
uploads[uploadedFile.fileKey]?.abort();
|
||||
|
||||
if (uploadedFile.atflId) {
|
||||
try {
|
||||
await attachService.deleteAttachmentByAtflId(uploadedFile.atflId);
|
||||
} catch (error) {
|
||||
console.error("검토의견 첨부파일 삭제 실패:", error);
|
||||
}
|
||||
}
|
||||
|
||||
setUploadedFile(null);
|
||||
setFormData((prev) => ({ ...prev, atchFileGroupId: "" }));
|
||||
}
|
||||
|
||||
function renderOpinionForm(reviewStep: ReviewStepCode) {
|
||||
if (editableReviewStep !== reviewStep) return null;
|
||||
if (reviewStep === "KECO" && !canWriteKecoOpinion) return null;
|
||||
|
||||
const uploading = uploadedFile ? (isUploading[uploadedFile.fileKey] ?? false) : false;
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-xl font-semibold">검토의견 작성</h4>
|
||||
<Table caption="검토의견 작성으로 의견내용, 검토결과, 첨부파일 정보 제공" variant="secondary">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[12rem]">의견내용</TableHead>
|
||||
<TableCell>
|
||||
<Textarea
|
||||
value={formData.rvwRsltCn}
|
||||
onChange={(event) =>
|
||||
setFormData((prev) => ({ ...prev, rvwRsltCn: event.target.value }))
|
||||
}
|
||||
placeholder="의견내용을 입력하세요"
|
||||
rows={4}
|
||||
className="min-h-[8rem] !text-xl"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>검토결과</TableHead>
|
||||
<TableCell>
|
||||
<FieldSet>
|
||||
<RadioGroup
|
||||
value={formData.rvwRsltAnmlyYn}
|
||||
onValueChange={(value) =>
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
rvwRsltAnmlyYn: value as ReviewOpinionFormData["rvwRsltAnmlyYn"],
|
||||
}))
|
||||
}
|
||||
>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<FieldLabel
|
||||
htmlFor={`rvw-opnn-normal-${reviewStep}`}
|
||||
className="!w-auto border-none"
|
||||
>
|
||||
<Field orientation="horizontal" className="w-auto">
|
||||
<RadioGroupItem value="N" id={`rvw-opnn-normal-${reviewStep}`} />
|
||||
<span className="font-normal">정상</span>
|
||||
</Field>
|
||||
</FieldLabel>
|
||||
<FieldLabel
|
||||
htmlFor={`rvw-opnn-abnormal-${reviewStep}`}
|
||||
className="!w-auto border-none"
|
||||
>
|
||||
<Field orientation="horizontal" className="w-auto">
|
||||
<RadioGroupItem value="Y" id={`rvw-opnn-abnormal-${reviewStep}`} />
|
||||
<span className="font-normal">이상</span>
|
||||
</Field>
|
||||
</FieldLabel>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</FieldSet>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>첨부파일</TableHead>
|
||||
<TableCell>
|
||||
<AttachmentFileField
|
||||
files={
|
||||
uploadedFile
|
||||
? [
|
||||
{
|
||||
fileKey: uploadedFile.fileKey,
|
||||
fileName: uploadedFile.fileName,
|
||||
onDownload: uploadedFile.atflId
|
||||
? () => downloadByAtflId(uploadedFile.atflId!)
|
||||
: undefined,
|
||||
},
|
||||
]
|
||||
: []
|
||||
}
|
||||
inputId={`rvw-opnn-${reviewStep}-file-input`}
|
||||
isUploadingByKey={isUploading}
|
||||
multiple={false}
|
||||
showAddButton={!uploadedFile}
|
||||
addDisabled={uploading}
|
||||
onAdd={(fileList) => void handleFileUpload(fileList)}
|
||||
onRemove={() => void handleFileRemove()}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{hasEnvSection ? (
|
||||
<SectionCollapsible title={`${REVIEW_STEP_LABELS.ENV} 검토의견`}>
|
||||
<div className="space-y-6">
|
||||
{envRvwList.length > 0 ? <TechReviewOpinionTable items={envRvwList} /> : null}
|
||||
{renderOpinionForm("ENV")}
|
||||
</div>
|
||||
</SectionCollapsible>
|
||||
) : null}
|
||||
{hasKecoSection ? (
|
||||
<SectionCollapsible title={`${REVIEW_STEP_LABELS.KECO} 검토의견`}>
|
||||
<div className="space-y-6">
|
||||
{kecoRvwList.length > 0 ? <TechReviewOpinionTable items={kecoRvwList} /> : null}
|
||||
{/*
|
||||
재원협의에서 환경공단 검토의견은 자동 생성
|
||||
{renderOpinionForm("KECO")}
|
||||
*/}
|
||||
</div>
|
||||
</SectionCollapsible>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
function TechReviewOpinionTable({ items }: TechReviewOpinionTableProps) {
|
||||
return (
|
||||
<div className="overflow-x-auto">
|
||||
<Table caption="검토의견 목록으로 의견자명, 의견내용, 검토결과, 첨부파일, 등록일시 정보 제공" variant="secondary">
|
||||
<colgroup>
|
||||
<col width="10%" />
|
||||
<col width="55%" />
|
||||
<col width="5%" />
|
||||
<col width="20%" />
|
||||
<col width="10%" />
|
||||
</colgroup>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>의견자명</TableHead>
|
||||
<TableHead>의견내용</TableHead>
|
||||
<TableHead>검토결과</TableHead>
|
||||
<TableHead>첨부파일</TableHead>
|
||||
<TableHead>등록일시</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{items.map((item) => (
|
||||
<TableRow key={`${item.fnrsCnsltnDocNo}_${item.rvwOpnnSn}`}>
|
||||
<TableCell align="center">{item.opnrNm}</TableCell>
|
||||
<TableCell align="left">{item.rvwRsltCn || "-"}</TableCell>
|
||||
<TableCell align="center">
|
||||
{item.rvwRsltAnmlyYn === "Y" ? "이상" : item.rvwRsltAnmlyYn === "N" ? "정상" : "-"}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
{item.atchFileInfo?.atflCd ? (
|
||||
<AttachmentDownloadLink
|
||||
atflCd={item.atchFileInfo.atflCd}
|
||||
fileName={item.atchFileInfo.orgnlFileNm || "다운로드"}
|
||||
/>
|
||||
) : (
|
||||
"-"
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
{item.regDt ? dayjs(item.regDt).format("YYYY-MM-DD HH:mm:ss") : "-"}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { TechReviewOpinionSection };
|
||||
"use client";
|
||||
|
||||
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
|
||||
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { getDefaultFileKey, useTus, useUserStore } from "@/shared/lib";
|
||||
import { AttachmentFileField, SectionCollapsible } from "@/shared/ui";
|
||||
import { Field, FieldLabel, FieldSet } from "@/shared/ui/field";
|
||||
import { attachService, useDownloadAttachmentByAtflId } from "@/entities/attach";
|
||||
import {
|
||||
fnrsCnsltnKeys,
|
||||
type FnrsCnsltnRvwOpnnResponseDto,
|
||||
fnrsCnsltnRvwOpnnService,
|
||||
} from "@/entities/fnrs-cnsltn";
|
||||
import { RvwOpnnListTable } from "@/widgets/pbadms-sprt";
|
||||
import {
|
||||
RadioGroup,
|
||||
RadioGroupItem,
|
||||
Table,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
Textarea,
|
||||
} from "@common/components";
|
||||
|
||||
import type {
|
||||
ReviewOpinionFormData,
|
||||
ReviewOpinionGetFormDataResult,
|
||||
} from "../../lib/review-opinion-utils";
|
||||
import type { ReviewStepCode } from "../../model/view-types";
|
||||
import { REVIEW_STEP_LABELS } from "../../model/view-types";
|
||||
|
||||
const RVW_OPNN_FILE_KEY_PREFIX = "rvw-opnn::";
|
||||
|
||||
interface TechReviewOpinionSectionProps {
|
||||
rvwList: FnrsCnsltnRvwOpnnResponseDto[];
|
||||
editableReviewStep?: ReviewStepCode;
|
||||
atrzSeComCdList?: string[];
|
||||
isAtrzLoading?: boolean;
|
||||
}
|
||||
|
||||
export interface TechReviewOpinionSectionHandle {
|
||||
getFormData: () => ReviewOpinionGetFormDataResult | null;
|
||||
resetForm: () => void;
|
||||
}
|
||||
|
||||
interface TechReviewOpinionTableProps {
|
||||
items: FnrsCnsltnRvwOpnnResponseDto[];
|
||||
}
|
||||
|
||||
interface UploadedOpinionFile {
|
||||
fileName: string;
|
||||
fileKey: string;
|
||||
atflId?: string;
|
||||
atflCd?: string;
|
||||
}
|
||||
|
||||
const EMPTY_FORM_DATA: ReviewOpinionFormData = {
|
||||
rvwRsltCn: "",
|
||||
rvwRsltAnmlyYn: "",
|
||||
atchFileGroupId: "",
|
||||
opnrSeComCd: "",
|
||||
};
|
||||
|
||||
function createRvwOpnnUploadKey(file: File) {
|
||||
return `${RVW_OPNN_FILE_KEY_PREFIX}${getDefaultFileKey(file)}`;
|
||||
}
|
||||
|
||||
function hasFormContent(formData: ReviewOpinionFormData) {
|
||||
return Boolean(formData.rvwRsltCn.trim() || formData.rvwRsltAnmlyYn || formData.atchFileGroupId);
|
||||
}
|
||||
|
||||
const TechReviewOpinionSection = forwardRef<
|
||||
TechReviewOpinionSectionHandle,
|
||||
TechReviewOpinionSectionProps
|
||||
>(function TechReviewOpinionSection(
|
||||
{ rvwList, editableReviewStep, atrzSeComCdList = [], isAtrzLoading = false },
|
||||
ref,
|
||||
) {
|
||||
const canWriteKecoOpinion = !isAtrzLoading && atrzSeComCdList.includes("ATRZ02");
|
||||
const [formData, setFormData] = useState<ReviewOpinionFormData>({
|
||||
...EMPTY_FORM_DATA,
|
||||
opnrSeComCd: editableReviewStep ?? "",
|
||||
});
|
||||
const [uploadedFile, setUploadedFile] = useState<UploadedOpinionFile | null>(null);
|
||||
const { mutate: downloadByAtflId } = useDownloadAttachmentByAtflId();
|
||||
|
||||
useEffect(() => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
opnrSeComCd: editableReviewStep ?? "",
|
||||
}));
|
||||
}, [editableReviewStep]);
|
||||
|
||||
const { uploadFileAsync, isUploading, uploads } = useTus({
|
||||
onSuccess: (atflCd, atflId, _file, fileKey) => {
|
||||
if (!atflCd || !atflId) return;
|
||||
|
||||
setFormData((prev) => ({ ...prev, atchFileGroupId: atflCd }));
|
||||
setUploadedFile((prev) => (prev?.fileKey === fileKey ? { ...prev, atflCd, atflId } : prev));
|
||||
},
|
||||
onError: (_error, _file, fileKey) => {
|
||||
setUploadedFile((prev) => (prev?.fileKey === fileKey ? null : prev));
|
||||
},
|
||||
});
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
getFormData: () => {
|
||||
if (!editableReviewStep || !hasFormContent(formData)) {
|
||||
return null;
|
||||
}
|
||||
if (editableReviewStep === "KECO" && !canWriteKecoOpinion) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
formData: {
|
||||
...formData,
|
||||
opnrSeComCd: editableReviewStep,
|
||||
},
|
||||
rvwList,
|
||||
};
|
||||
},
|
||||
resetForm: () => {
|
||||
setFormData({
|
||||
...EMPTY_FORM_DATA,
|
||||
opnrSeComCd: editableReviewStep ?? "",
|
||||
});
|
||||
setUploadedFile(null);
|
||||
},
|
||||
}),
|
||||
[canWriteKecoOpinion, editableReviewStep, formData, rvwList],
|
||||
);
|
||||
|
||||
const envRvwList = rvwList.filter((item) => item.opnrSeComCd === "ENV");
|
||||
const kecoRvwList = rvwList.filter((item) => item.opnrSeComCd === "KECO");
|
||||
|
||||
const hasEnvSection = envRvwList.length > 0 || editableReviewStep === "ENV";
|
||||
const hasKecoSection = kecoRvwList.length > 0 || editableReviewStep === "KECO";
|
||||
|
||||
if (!hasEnvSection && !hasKecoSection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
async function handleFileUpload(files: FileList | null) {
|
||||
if (!files || files.length === 0) return;
|
||||
|
||||
const file = files[0];
|
||||
const fileKey = createRvwOpnnUploadKey(file);
|
||||
const existingAtflCd = formData.atchFileGroupId;
|
||||
|
||||
setUploadedFile({ fileName: file.name, fileKey });
|
||||
|
||||
try {
|
||||
await uploadFileAsync(file, {
|
||||
fileKey,
|
||||
metadata: existingAtflCd ? { atflCd: existingAtflCd } : undefined,
|
||||
});
|
||||
} catch {
|
||||
// onError callback handles cleanup
|
||||
}
|
||||
}
|
||||
|
||||
async function handleFileRemove() {
|
||||
if (!uploadedFile) return;
|
||||
|
||||
uploads[uploadedFile.fileKey]?.abort();
|
||||
|
||||
if (uploadedFile.atflId) {
|
||||
try {
|
||||
await attachService.deleteAttachmentByAtflId(uploadedFile.atflId);
|
||||
} catch (error) {
|
||||
console.error("검토의견 첨부파일 삭제 실패:", error);
|
||||
}
|
||||
}
|
||||
|
||||
setUploadedFile(null);
|
||||
setFormData((prev) => ({ ...prev, atchFileGroupId: "" }));
|
||||
}
|
||||
|
||||
function isReviewTurn(reviewStep: ReviewStepCode) {
|
||||
if (editableReviewStep !== reviewStep) return false;
|
||||
if (reviewStep === "KECO" && !canWriteKecoOpinion) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function renderOpinionForm(reviewStep: ReviewStepCode) {
|
||||
if (!isReviewTurn(reviewStep)) return null;
|
||||
|
||||
const uploading = uploadedFile ? (isUploading[uploadedFile.fileKey] ?? false) : false;
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-xl font-semibold">검토의견 작성</h4>
|
||||
<Table
|
||||
caption="검토의견 작성으로 의견내용, 검토결과, 첨부파일 정보 제공"
|
||||
variant="secondary"
|
||||
>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[12rem]">의견내용</TableHead>
|
||||
<TableCell>
|
||||
<Textarea
|
||||
value={formData.rvwRsltCn}
|
||||
onChange={(event) =>
|
||||
setFormData((prev) => ({ ...prev, rvwRsltCn: event.target.value }))
|
||||
}
|
||||
placeholder="의견내용을 입력하세요"
|
||||
rows={4}
|
||||
className="min-h-[8rem] !text-xl"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>검토결과</TableHead>
|
||||
<TableCell>
|
||||
<FieldSet>
|
||||
<RadioGroup
|
||||
value={formData.rvwRsltAnmlyYn}
|
||||
onValueChange={(value) =>
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
rvwRsltAnmlyYn: value as ReviewOpinionFormData["rvwRsltAnmlyYn"],
|
||||
}))
|
||||
}
|
||||
>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<FieldLabel
|
||||
htmlFor={`rvw-opnn-normal-${reviewStep}`}
|
||||
className="!w-auto border-none"
|
||||
>
|
||||
<Field orientation="horizontal" className="w-auto">
|
||||
<RadioGroupItem value="N" id={`rvw-opnn-normal-${reviewStep}`} />
|
||||
<span className="font-normal">정상</span>
|
||||
</Field>
|
||||
</FieldLabel>
|
||||
<FieldLabel
|
||||
htmlFor={`rvw-opnn-abnormal-${reviewStep}`}
|
||||
className="!w-auto border-none"
|
||||
>
|
||||
<Field orientation="horizontal" className="w-auto">
|
||||
<RadioGroupItem value="Y" id={`rvw-opnn-abnormal-${reviewStep}`} />
|
||||
<span className="font-normal">이상</span>
|
||||
</Field>
|
||||
</FieldLabel>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</FieldSet>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>첨부파일</TableHead>
|
||||
<TableCell>
|
||||
<AttachmentFileField
|
||||
files={
|
||||
uploadedFile
|
||||
? [
|
||||
{
|
||||
fileKey: uploadedFile.fileKey,
|
||||
fileName: uploadedFile.fileName,
|
||||
onDownload: uploadedFile.atflId
|
||||
? () => downloadByAtflId(uploadedFile.atflId!)
|
||||
: undefined,
|
||||
},
|
||||
]
|
||||
: []
|
||||
}
|
||||
inputId={`rvw-opnn-${reviewStep}-file-input`}
|
||||
isUploadingByKey={isUploading}
|
||||
multiple={false}
|
||||
showAddButton={!uploadedFile}
|
||||
addDisabled={uploading}
|
||||
onAdd={(fileList) => void handleFileUpload(fileList)}
|
||||
onRemove={() => void handleFileRemove()}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{hasEnvSection ? (
|
||||
<SectionCollapsible title={`${REVIEW_STEP_LABELS.ENV} 검토의견`}>
|
||||
<div className="space-y-6">
|
||||
{envRvwList.length > 0 ? (
|
||||
<TechReviewOpinionTable items={envRvwList} />
|
||||
) : null}
|
||||
{renderOpinionForm("ENV")}
|
||||
</div>
|
||||
</SectionCollapsible>
|
||||
) : null}
|
||||
{hasKecoSection ? (
|
||||
<SectionCollapsible title={`${REVIEW_STEP_LABELS.KECO} 검토의견`}>
|
||||
<div className="space-y-6">
|
||||
{kecoRvwList.length > 0 ? (
|
||||
<TechReviewOpinionTable items={kecoRvwList} />
|
||||
) : null}
|
||||
{/*
|
||||
재원협의에서 환경공단 검토의견은 자동 생성
|
||||
{renderOpinionForm("KECO")}
|
||||
*/}
|
||||
</div>
|
||||
</SectionCollapsible>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
function getRvwOpnnIds(item: FnrsCnsltnRvwOpnnResponseDto) {
|
||||
if (!item.fnrsCnsltnDocNo || item.rvwOpnnSn == null) {
|
||||
throw new Error("검토의견 식별값이 없습니다.");
|
||||
}
|
||||
return [item.fnrsCnsltnDocNo, item.rvwOpnnSn] as const;
|
||||
}
|
||||
|
||||
function TechReviewOpinionTable({ items }: TechReviewOpinionTableProps) {
|
||||
const queryClient = useQueryClient();
|
||||
const currentUserUuid = useUserStore((state) => state.userInfo?.userUnqId);
|
||||
|
||||
return (
|
||||
<RvwOpnnListTable
|
||||
items={items}
|
||||
getRowKey={(item) => `${item.fnrsCnsltnDocNo}_${item.rvwOpnnSn}`}
|
||||
currentUserUuid={currentUserUuid}
|
||||
onUpdate={async (item, body) => {
|
||||
await fnrsCnsltnRvwOpnnService.update(...getRvwOpnnIds(item), body);
|
||||
await queryClient.invalidateQueries({ queryKey: fnrsCnsltnKeys.all });
|
||||
}}
|
||||
onDelete={async (item) => {
|
||||
await fnrsCnsltnRvwOpnnService.delete(...getRvwOpnnIds(item));
|
||||
await queryClient.invalidateQueries({ queryKey: fnrsCnsltnKeys.all });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export { TechReviewOpinionSection };
|
||||
|
||||
@@ -3,7 +3,6 @@ export const INSTL_SNCN_VIEW_BUTTON_ACTION = {
|
||||
aply: "aply",
|
||||
del: "del",
|
||||
instlSncnAncmnt: "instlSncnAncmnt",
|
||||
envoSbmsn: "envoSbmsn",
|
||||
rjct: "rjct",
|
||||
sncnAprv: "sncnAprv",
|
||||
rvwRply: "rvwRply",
|
||||
@@ -33,14 +32,16 @@ const ROLE_STATUS_BUTTON_ACTIONS: Record<string, Record<string, InstlSncnViewBut
|
||||
],
|
||||
TA: [INSTL_SNCN_VIEW_BUTTON_ACTION.instlSncnAncmnt],
|
||||
},
|
||||
ROLE_USER04: {
|
||||
ST: [INSTL_SNCN_VIEW_BUTTON_ACTION.envoSbmsn, INSTL_SNCN_VIEW_BUTTON_ACTION.rjct],
|
||||
EP: [INSTL_SNCN_VIEW_BUTTON_ACTION.sncnAprv],
|
||||
},
|
||||
ROLE_USER03: {
|
||||
LE: [INSTL_SNCN_VIEW_BUTTON_ACTION.rvwRply, INSTL_SNCN_VIEW_BUTTON_ACTION.rqstTechRvw],
|
||||
TP: [INSTL_SNCN_VIEW_BUTTON_ACTION.rvwRply],
|
||||
},
|
||||
// 환경청 단계 제거: 기존 환경청(LE·TP) 기능을 광역자치단체가 수행
|
||||
ROLE_USER04: {
|
||||
ST: [
|
||||
INSTL_SNCN_VIEW_BUTTON_ACTION.rvwRply,
|
||||
INSTL_SNCN_VIEW_BUTTON_ACTION.rqstTechRvw,
|
||||
INSTL_SNCN_VIEW_BUTTON_ACTION.rjct,
|
||||
],
|
||||
TP: [INSTL_SNCN_VIEW_BUTTON_ACTION.rvwRply],
|
||||
EP: [INSTL_SNCN_VIEW_BUTTON_ACTION.sncnAprv],
|
||||
},
|
||||
};
|
||||
|
||||
const ROLE_USER01_ATRZ_STATUS_CODES = ["ER", "TE"] as const;
|
||||
|
||||
@@ -55,16 +55,8 @@ export const instlSncnRegistSchema = z.object({
|
||||
tstmDesignAmt: z.number(),
|
||||
}),
|
||||
),
|
||||
cklstResultList: z.array(
|
||||
instlSncnCklstResultItemSchema.extend({
|
||||
cklstSn: z.number(),
|
||||
cklstArtclSn: z.number(),
|
||||
cklstRvwSn: z.number(),
|
||||
rvwStpComCd: z.string(),
|
||||
selected: z.enum(["Y", "N"]).nullish(),
|
||||
picRvwCn: z.string().nullish(),
|
||||
}),
|
||||
),
|
||||
// 설치인가 화면에서 체크리스트 미사용: 숨겨진 값 때문에 신청·저장 검증이 막히지 않도록 필수값을 두지 않음
|
||||
cklstResultList: z.array(instlSncnCklstResultItemSchema),
|
||||
esntlAtchFileList: z.array(
|
||||
instlSncnEsntlAtchFileItemSchema.extend({
|
||||
esntlAtchFileId: z.string(),
|
||||
|
||||
@@ -50,7 +50,7 @@ export function getEditableReviewStep(
|
||||
dtlInstCd?: string,
|
||||
prgrsSttsComCd?: string,
|
||||
): ReviewStepCode | undefined {
|
||||
if (dtlInstCd === "ROLE_USER03" && (prgrsSttsComCd === "LE" || prgrsSttsComCd === "TP")) {
|
||||
if (dtlInstCd === "ROLE_USER04" && (prgrsSttsComCd === "ST" || prgrsSttsComCd === "TP")) {
|
||||
return "ENV";
|
||||
}
|
||||
|
||||
|
||||
@@ -1,217 +1,231 @@
|
||||
"use client";
|
||||
|
||||
import { Fragment } from "react";
|
||||
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
|
||||
import { dayjs } from "@/shared/lib";
|
||||
import type { InstlSncnMngResponseDto } from "@/entities/instl-sncn";
|
||||
import { Badge } from "@common/components";
|
||||
|
||||
const columnHelper = createColumnHelper<InstlSncnMngResponseDto>();
|
||||
|
||||
const MAIN_STEP_LABELS = {
|
||||
apply: "설치인가 신청",
|
||||
envoReview: "설치인가 검토",
|
||||
techReview: "기술검토",
|
||||
reply: "검토결과 회신",
|
||||
} as const;
|
||||
|
||||
export const PROGRESS_STATUS_COLUMNS = [
|
||||
{ label: "설치인가 신청 (시군)", sttsComCd: "ST" },
|
||||
{ label: "설치인가 신청 (시도)", sttsComCd: "LE" },
|
||||
{ label: "설치인가 검토", sttsComCd: "ER" },
|
||||
{ label: "기술검토", sttsComCd: "TE" },
|
||||
{ label: "기술검토 회신", sttsComCd: "TP" },
|
||||
{ label: "검토결과 회신", sttsComCd: "EP" },
|
||||
{ label: "설치인가 승인", sttsComCd: "TA" },
|
||||
];
|
||||
|
||||
type BadgeState = "completed" | "current" | "pending";
|
||||
|
||||
interface ProgressBadge {
|
||||
label: string;
|
||||
state: BadgeState;
|
||||
}
|
||||
|
||||
function buildProgressBadges(prgrsSttsComCd: string, techRplyHstryYn: "Y" | "N"): ProgressBadge[] {
|
||||
if (prgrsSttsComCd === "SV") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
if (prgrsSttsComCd === "ST") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "current" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
if (prgrsSttsComCd === "LE") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "current" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
if (prgrsSttsComCd === "ER" || prgrsSttsComCd === "TE" || prgrsSttsComCd === "SL") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "current" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
if (prgrsSttsComCd === "TP" || prgrsSttsComCd === "EP") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "completed" },
|
||||
{
|
||||
label: MAIN_STEP_LABELS.techReview,
|
||||
state: techRplyHstryYn === "Y" ? "completed" : "pending",
|
||||
},
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "current" },
|
||||
];
|
||||
}
|
||||
|
||||
if (prgrsSttsComCd === "TA" || prgrsSttsComCd === "TB") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "completed" },
|
||||
{
|
||||
label: MAIN_STEP_LABELS.techReview,
|
||||
state: techRplyHstryYn === "Y" ? "completed" : "pending",
|
||||
},
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "completed" },
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
function getProgressStatusBadge(prgrsSttsComCd?: string | null) {
|
||||
if (prgrsSttsComCd === "SL") {
|
||||
return <Badge color="orange">보완요청</Badge>;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getBadgeColor(state: BadgeState) {
|
||||
switch (state) {
|
||||
case "completed":
|
||||
return "green";
|
||||
case "current":
|
||||
return "blue";
|
||||
case "pending":
|
||||
return "lightGray";
|
||||
}
|
||||
}
|
||||
|
||||
function getBadgeStateText(state: BadgeState) {
|
||||
switch (state) {
|
||||
case "completed":
|
||||
return "완료";
|
||||
case "current":
|
||||
return "진행중";
|
||||
case "pending":
|
||||
return "대기";
|
||||
}
|
||||
}
|
||||
|
||||
function formatOptional(value: string | null | undefined) {
|
||||
return value?.trim() ? value : "-";
|
||||
}
|
||||
|
||||
interface ProgressStepBadgeProps {
|
||||
prgrsSttsComCd: string;
|
||||
techRplyHstryYn: "Y" | "N";
|
||||
}
|
||||
|
||||
function ProgressStepBadge({ prgrsSttsComCd, techRplyHstryYn }: ProgressStepBadgeProps) {
|
||||
const badges = buildProgressBadges(prgrsSttsComCd, techRplyHstryYn);
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-center justify-start">
|
||||
{badges.map((badge, index) => (
|
||||
<Fragment key={badge.label}>
|
||||
{index > 0 && <span className="mx-2">></span>}
|
||||
<Badge color={getBadgeColor(badge.state)}>
|
||||
{badge.label}({getBadgeStateText(badge.state)})
|
||||
</Badge>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function createInstlSncnColumns() {
|
||||
return [
|
||||
columnHelper.accessor("sido", {
|
||||
header: "시도",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
}),
|
||||
columnHelper.accessor("gugun", {
|
||||
header: "시군구",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
}),
|
||||
columnHelper.accessor("bizNm", {
|
||||
header: "사업명",
|
||||
meta: { align: "left" },
|
||||
cell: ({ row, getValue }) => (
|
||||
<>
|
||||
{getProgressStatusBadge(row.original.prgrsSttsComCd)} {formatOptional(getValue())}
|
||||
</>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("taskSeCdNm", {
|
||||
header: "업무구분",
|
||||
meta: { align: "center" },
|
||||
cell: ({ row }) => formatOptional(row.original.taskSeCdNm || row.original.taskSeComCd),
|
||||
}),
|
||||
columnHelper.accessor("aplcntNm", {
|
||||
header: "담당자",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
}),
|
||||
columnHelper.accessor("envoSeCdNm", {
|
||||
header: "해당기관",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: "progress",
|
||||
header: "진행상황",
|
||||
meta: { align: "center" },
|
||||
cell: ({ row }) => (
|
||||
<ProgressStepBadge
|
||||
prgrsSttsComCd={row.original.prgrsSttsComCd || ""}
|
||||
techRplyHstryYn={row.original.techRplyHstryYn || "N"}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("regDt", {
|
||||
header: "신청일",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => {
|
||||
const value = getValue();
|
||||
return value ? dayjs(value).format("YYYY-MM-DD") : "-";
|
||||
},
|
||||
}),
|
||||
];
|
||||
}
|
||||
"use client";
|
||||
|
||||
import { Fragment } from "react";
|
||||
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
|
||||
import { dayjs } from "@/shared/lib";
|
||||
import type { InstlSncnMngResponseDto } from "@/entities/instl-sncn";
|
||||
import { Badge } from "@common/components";
|
||||
|
||||
const columnHelper = createColumnHelper<InstlSncnMngResponseDto>();
|
||||
|
||||
const MAIN_STEP_LABELS = {
|
||||
apply: "설치인가 신청",
|
||||
envoReview: "설치인가 검토",
|
||||
techReview: "기술검토",
|
||||
reply: "검토결과 회신",
|
||||
} as const;
|
||||
|
||||
export const PROGRESS_STATUS_COLUMNS = [
|
||||
{ label: "설치인가 신청 (시군)", sttsComCd: "ST" },
|
||||
{ label: "설치인가 신청 (시도)", sttsComCd: "LE" },
|
||||
{ label: "설치인가 검토", sttsComCd: "ER" },
|
||||
{ label: "기술검토", sttsComCd: "TE" },
|
||||
{ label: "기술검토 회신", sttsComCd: "TP" },
|
||||
{ label: "검토결과 회신", sttsComCd: "EP" },
|
||||
{ label: "설치인가 승인", sttsComCd: "TA" },
|
||||
];
|
||||
|
||||
type BadgeState = "completed" | "current" | "pending";
|
||||
|
||||
interface ProgressBadge {
|
||||
label: string;
|
||||
state: BadgeState;
|
||||
}
|
||||
|
||||
function buildProgressBadges(prgrsSttsComCd: string, techRplyHstryYn: "Y" | "N"): ProgressBadge[] {
|
||||
if (prgrsSttsComCd === "SV") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
if (prgrsSttsComCd === "ST") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "current" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
if (prgrsSttsComCd === "LE") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "current" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
if (prgrsSttsComCd === "ER" || prgrsSttsComCd === "TE" || prgrsSttsComCd === "SL") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "current" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
if (prgrsSttsComCd === "TP" || prgrsSttsComCd === "EP") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "completed" },
|
||||
{
|
||||
label: MAIN_STEP_LABELS.techReview,
|
||||
state: techRplyHstryYn === "Y" ? "completed" : "pending",
|
||||
},
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "current" },
|
||||
];
|
||||
}
|
||||
|
||||
if (prgrsSttsComCd === "TA" || prgrsSttsComCd === "TB") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "completed" },
|
||||
{
|
||||
label: MAIN_STEP_LABELS.techReview,
|
||||
state: techRplyHstryYn === "Y" ? "completed" : "pending",
|
||||
},
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "completed" },
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
function getProgressStatusBadge(prgrsSttsComCd?: string | null) {
|
||||
if (prgrsSttsComCd === "SL") {
|
||||
return <Badge color="orange">보완요청</Badge>;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getBadgeColor(state: BadgeState) {
|
||||
switch (state) {
|
||||
case "completed":
|
||||
return "green";
|
||||
case "current":
|
||||
return "blue";
|
||||
case "pending":
|
||||
return "lightGray";
|
||||
}
|
||||
}
|
||||
|
||||
function getBadgeStateText(state: BadgeState) {
|
||||
switch (state) {
|
||||
case "completed":
|
||||
return "완료";
|
||||
case "current":
|
||||
return "진행중";
|
||||
case "pending":
|
||||
return "대기";
|
||||
}
|
||||
}
|
||||
|
||||
/** 단계명을 2줄로 분리: "설치인가 신청" → ["설치인가", "신청"], "기술검토" → ["기술검토", ""] */
|
||||
function splitStepLabel(label: string) {
|
||||
const [title = "", ...rest] = label.split(" ");
|
||||
return { title, detail: rest.join(" ") };
|
||||
}
|
||||
|
||||
function formatOptional(value: string | null | undefined) {
|
||||
return value?.trim() ? value : "-";
|
||||
}
|
||||
|
||||
interface ProgressStepBadgeProps {
|
||||
prgrsSttsComCd: string;
|
||||
techRplyHstryYn: "Y" | "N";
|
||||
}
|
||||
|
||||
function ProgressStepBadge({ prgrsSttsComCd, techRplyHstryYn }: ProgressStepBadgeProps) {
|
||||
const badges = buildProgressBadges(prgrsSttsComCd, techRplyHstryYn);
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-center justify-center">
|
||||
{badges.map((badge, index) => (
|
||||
<Fragment key={badge.label}>
|
||||
{index > 0 && <span className="mx-2">></span>}
|
||||
{/* 1줄: 단계명 / 2줄: 세부단계 + 상태 (예: 설치인가 / 신청완료, 기술검토 / 완료) */}
|
||||
<Badge
|
||||
color={getBadgeColor(badge.state)}
|
||||
className="w-[100px] flex-col justify-center !px-0 py-1 text-center leading-tight"
|
||||
>
|
||||
<span>{splitStepLabel(badge.label).title}</span>
|
||||
<span>
|
||||
{splitStepLabel(badge.label).detail}
|
||||
{getBadgeStateText(badge.state)}
|
||||
</span>
|
||||
</Badge>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function createInstlSncnColumns() {
|
||||
return [
|
||||
columnHelper.accessor("sido", {
|
||||
header: "시도",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
}),
|
||||
columnHelper.accessor("gugun", {
|
||||
header: "시군구",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
}),
|
||||
columnHelper.accessor("bizNm", {
|
||||
header: "사업명",
|
||||
meta: { align: "left" },
|
||||
cell: ({ row, getValue }) => (
|
||||
<>
|
||||
{getProgressStatusBadge(row.original.prgrsSttsComCd)} {formatOptional(getValue())}
|
||||
</>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("taskSeCdNm", {
|
||||
header: "업무구분",
|
||||
meta: { align: "center" },
|
||||
cell: ({ row }) => formatOptional(row.original.taskSeCdNm || row.original.taskSeComCd),
|
||||
}),
|
||||
columnHelper.accessor("aplcntNm", {
|
||||
header: "담당자",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
}),
|
||||
columnHelper.accessor("envoSeCdNm", {
|
||||
header: "해당기관",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: "progress",
|
||||
header: "진행상황",
|
||||
meta: { align: "center" },
|
||||
cell: ({ row }) => (
|
||||
<ProgressStepBadge
|
||||
prgrsSttsComCd={row.original.prgrsSttsComCd || ""}
|
||||
techRplyHstryYn={row.original.techRplyHstryYn || "N"}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("regDt", {
|
||||
header: "신청일",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => {
|
||||
const value = getValue();
|
||||
return value ? dayjs(value).format("YYYY-MM-DD") : "-";
|
||||
},
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -50,13 +50,13 @@ export const REVIEW_STEP_CODES: ReviewStepCode[] = ["LOCAL", "ENV", "KECO"];
|
||||
|
||||
export const CHECKLIST_TAB_LABELS: Record<ReviewStepCode, string> = {
|
||||
LOCAL: "자가체크",
|
||||
ENV: "환경청검토",
|
||||
ENV: "광역자치단체검토",
|
||||
KECO: "기술검토",
|
||||
};
|
||||
|
||||
export const REVIEW_STEP_LABELS: Record<ReviewStepCode, string> = {
|
||||
LOCAL: "지자체",
|
||||
ENV: "환경청",
|
||||
ENV: "광역자치단체",
|
||||
KECO: "환경공단",
|
||||
};
|
||||
|
||||
@@ -89,7 +89,7 @@ export const INSTL_PROGRESS_ORDER = [
|
||||
|
||||
export const ROLE_TO_REVIEW_STEP: Record<string, ReviewStepCode> = {
|
||||
ROLE_USER05: "LOCAL",
|
||||
ROLE_USER03: "ENV",
|
||||
ROLE_USER04: "ENV",
|
||||
ROLE_USER01: "KECO",
|
||||
};
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ import {
|
||||
type InstlSncnViewButtonAction,
|
||||
needsRoleUser01AtrzResolve,
|
||||
} from "../../lib/button-config";
|
||||
import { computeReviewStepChecklistProgress } from "../../lib/checklist-utils";
|
||||
import {
|
||||
buildSavePayload,
|
||||
buildSupplementReqPayload,
|
||||
@@ -179,11 +178,6 @@ function FloatingActionButtons({
|
||||
}
|
||||
|
||||
async function handleAplyClick() {
|
||||
const { percent } = computeReviewStepChecklistProgress(
|
||||
data.cklstResultList ?? [],
|
||||
checklistFormState.results,
|
||||
"LOCAL",
|
||||
);
|
||||
const payload: InstlSncnApplySaveRequestDto = buildSavePayload(
|
||||
data,
|
||||
checklistFormState,
|
||||
@@ -191,11 +185,6 @@ function FloatingActionButtons({
|
||||
"ST",
|
||||
);
|
||||
|
||||
if (percent !== 100) {
|
||||
alert("모든 체크리스트 항목을 체크해야 신청이 가능합니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
await executeAction({
|
||||
action: INSTL_SNCN_VIEW_BUTTON_ACTION.aply,
|
||||
request: payload,
|
||||
@@ -231,22 +220,6 @@ function FloatingActionButtons({
|
||||
});
|
||||
}
|
||||
|
||||
async function handleEnvoSbmsnClick() {
|
||||
const payload: DocPrgrsUpdateRequestDto = {
|
||||
docNo,
|
||||
docSeComCd,
|
||||
prgrsSttsComCd: "LE",
|
||||
};
|
||||
await executeAction({
|
||||
action: INSTL_SNCN_VIEW_BUTTON_ACTION.envoSbmsn,
|
||||
request: payload,
|
||||
successMessage: "제출되었습니다.",
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: instlSncnKeys.list(),
|
||||
});
|
||||
}
|
||||
|
||||
async function handleRjctClick() {
|
||||
const payload: DocPrgrsUpdateRequestDto = {
|
||||
docNo,
|
||||
@@ -284,16 +257,6 @@ function FloatingActionButtons({
|
||||
}
|
||||
|
||||
async function handleRvwRplyClick() {
|
||||
const { percent } = computeReviewStepChecklistProgress(
|
||||
data.cklstResultList ?? [],
|
||||
checklistFormState.results,
|
||||
"ENV",
|
||||
);
|
||||
|
||||
if (percent !== 100) {
|
||||
alert("모든 체크리스트 항목을 체크해야 검토결과 회신이 가능합니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = buildTechReviewReplyPayload(
|
||||
data,
|
||||
@@ -369,16 +332,6 @@ function FloatingActionButtons({
|
||||
}
|
||||
|
||||
async function handleRvwCmptnClick() {
|
||||
const { percent } = computeReviewStepChecklistProgress(
|
||||
data.cklstResultList ?? [],
|
||||
checklistFormState.results,
|
||||
"KECO",
|
||||
);
|
||||
|
||||
if (percent !== 100) {
|
||||
alert("모든 체크리스트 항목을 체크해야 검토완료가 가능합니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
...buildTechReviewReplyPayload(
|
||||
@@ -447,11 +400,6 @@ function FloatingActionButtons({
|
||||
설치인가 고시
|
||||
</Button>
|
||||
)}
|
||||
{shouldShowButton(INSTL_SNCN_VIEW_BUTTON_ACTION.envoSbmsn) && (
|
||||
<Button onClick={handleEnvoSbmsnClick} disabled={isProcessing}>
|
||||
환경청제출
|
||||
</Button>
|
||||
)}
|
||||
{shouldShowButton(INSTL_SNCN_VIEW_BUTTON_ACTION.rjct) && (
|
||||
<Button color="red" onClick={handleRjctClick} disabled={isProcessing}>
|
||||
반려
|
||||
@@ -618,7 +566,6 @@ const buttonActionHandlers: Record<InstlSncnViewButtonAction, ActionHandler> = {
|
||||
aply: handleSaveApply,
|
||||
del: handleDel,
|
||||
instlSncnAncmnt: handleChangePrgrsSttsComCd,
|
||||
envoSbmsn: handleChangePrgrsSttsComCd,
|
||||
rjct: handleChangePrgrsSttsComCd,
|
||||
sncnAprv: handleChangePrgrsSttsComCd,
|
||||
rvwRply: handleTechReview,
|
||||
|
||||
@@ -20,9 +20,7 @@ import {
|
||||
AttachmentsFormSection,
|
||||
BusinessCostFormSection,
|
||||
BusinessScaleFormSection,
|
||||
ChecklistFormSection,
|
||||
ContactInfoFormSection,
|
||||
isFormChecklistComplete,
|
||||
isFormEsntlAtchComplete,
|
||||
PrivacyConsentSection,
|
||||
ProgressStatusSection,
|
||||
@@ -154,11 +152,6 @@ function InstlFormPage({ mode, initialData, createDefaults }: InstlFormPageProps
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isFormChecklistComplete(data.cklstResultList)) {
|
||||
alert("모든 체크리스트 항목을 체크해야 신청이 가능합니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isFormEsntlAtchComplete(essentialFiles, data.esntlAtchFileList)) {
|
||||
alert("필수 첨부파일을 모두 등록해야 신청이 가능합니다.");
|
||||
return;
|
||||
@@ -228,7 +221,6 @@ function InstlFormPage({ mode, initialData, createDefaults }: InstlFormPageProps
|
||||
<PrivacyConsentSection />
|
||||
<BusinessScaleFormSection />
|
||||
<BusinessCostFormSection />
|
||||
<ChecklistFormSection docSeComCd="INSTL" fieldPrefix="instlSncnMng" />
|
||||
<AttachmentsFormSection
|
||||
docSeComCd="INSTL"
|
||||
fieldPrefix="instlSncnMng"
|
||||
|
||||
@@ -77,8 +77,8 @@ function InstlListPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4 px-[1.8rem] pb-6">
|
||||
<section className="card card-border mx-0 bg-background">
|
||||
<div className="card card-border !mt-6">
|
||||
<div className="card card-border">
|
||||
<form className="flex flex-wrap items-center gap-4 p-2" onSubmit={handleSearch}>
|
||||
<div className="flex items-center gap-2">
|
||||
<Label htmlFor="sidoComCd" className="whitespace-nowrap">
|
||||
@@ -145,7 +145,7 @@ function InstlListPage() {
|
||||
</div>
|
||||
<Button type="submit">조회</Button>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<InstlList
|
||||
searchParams={searchParams}
|
||||
|
||||
@@ -82,15 +82,15 @@ function InstlList({ searchParams, pagination, onPaginationChange }: InstlListPr
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className="card card-border mx-0 bg-background pb-8 pt-0">
|
||||
<div className="card card-border pt-0">
|
||||
<div className="card-header flex items-center justify-between">
|
||||
<h3 className="text-2xl font-semibold">설치인가 목록</h3>
|
||||
<h3 className="text-2xl font-medium text-label">설치인가 목록</h3>
|
||||
<Button type="button" color="green" onClick={handleRegisterClick}>
|
||||
신청
|
||||
</Button>
|
||||
</div>
|
||||
{tableComponent}
|
||||
</section>
|
||||
<div className="card !m-[1.2rem] !p-0">{tableComponent}</div>
|
||||
</div>
|
||||
<PbadmsTaskApplyDialog
|
||||
open={isTaskSeDialogOpen}
|
||||
onOpenChange={setIsTaskSeDialogOpen}
|
||||
|
||||
@@ -1,149 +1,141 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo, useRef } from "react";
|
||||
|
||||
import { useUserStore } from "@/shared/lib";
|
||||
import { SectionCollapsibleGroup, SectionCollapsibleToggle } from "@/shared/ui";
|
||||
import { ElaprDocPrgrsStatus } from "@/entities/elapr";
|
||||
import { useInstlSncnDetail } from "@/entities/instl-sncn";
|
||||
import { useRvwMnpwMngLoginUserList } from "@/entities/rvw-mnpw";
|
||||
import {
|
||||
AttachmentsViewSection,
|
||||
ProgressStatusSection,
|
||||
SupplementDemandSection,
|
||||
} from "@/widgets/pbadms-sprt";
|
||||
import { Skeleton } from "@common/components";
|
||||
|
||||
import { needsRoleUser01AtrzResolve } from "../lib/button-config";
|
||||
import { getEditableReviewStep } from "../lib/review-step-utils";
|
||||
import { EMPTY_CKLST_RESULT_LIST, useChecklistFormState } from "../lib/use-checklist-form-state";
|
||||
import { PROGRESS_STATUS_COLUMNS } from "../model/columns";
|
||||
|
||||
import { FloatingActionButtons } from "./components/floating-action-buttons";
|
||||
import { BasicInfoSection } from "./sections/basic-info-section";
|
||||
import { BusinessCostSection } from "./sections/business-cost-section";
|
||||
import { BusinessScaleSection } from "./sections/business-scale-section";
|
||||
import { ChecklistSection } from "./sections/checklist-section";
|
||||
import { ContactInfoSection } from "./sections/contact-info-section";
|
||||
import {
|
||||
TechReviewOpinionSection,
|
||||
type TechReviewOpinionSectionHandle,
|
||||
} from "./sections/tech-review-opinion-section";
|
||||
|
||||
interface InstlViewPageProps {
|
||||
id: string;
|
||||
}
|
||||
|
||||
function InstlViewPage({ id }: InstlViewPageProps) {
|
||||
const userInfo = useUserStore((state) => state.userInfo);
|
||||
const techReviewOpinionRef = useRef<TechReviewOpinionSectionHandle>(null);
|
||||
const { data, isLoading, isError } = useInstlSncnDetail(id);
|
||||
const { formState: checklistFormState, setFormState: setChecklistFormState } =
|
||||
useChecklistFormState(data?.cklstResultList ?? EMPTY_CKLST_RESULT_LIST);
|
||||
|
||||
const prgrsSttsComCd = data?.detDto?.prgrsSttsComCd || "";
|
||||
const needsAtrzResolve = needsRoleUser01AtrzResolve(userInfo?.dtlInstCd, prgrsSttsComCd);
|
||||
const { data: rvwLoginUserData, isLoading: isRvwLoginUserLoading } = useRvwMnpwMngLoginUserList(
|
||||
{},
|
||||
{ enabled: needsAtrzResolve },
|
||||
);
|
||||
const atrzSeComCdList = useMemo(
|
||||
() => rvwLoginUserData?.content?.map((item) => item.atrzSeComCd) ?? [],
|
||||
[rvwLoginUserData],
|
||||
);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="mt-6 flex flex-col gap-4 px-6">
|
||||
<div className="flex flex-col gap-2">
|
||||
<Skeleton className="h-[4rem] rounded-xl" />
|
||||
<Skeleton className="h-[16rem] rounded-xl" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Skeleton className="h-[4rem] rounded-xl" />
|
||||
<Skeleton className="h-[16rem] rounded-xl" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Skeleton className="h-[4rem] rounded-xl" />
|
||||
<Skeleton className="h-[32rem] rounded-xl" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isError || !data) {
|
||||
return <div className="mt-6 text-[1.4rem]">상세 정보를 찾을 수 없습니다.</div>;
|
||||
}
|
||||
|
||||
const {
|
||||
detDto,
|
||||
bizSclList,
|
||||
bizCstList,
|
||||
cklstResultList = [],
|
||||
esntlFileList,
|
||||
etcFileList,
|
||||
rvwList = [],
|
||||
splmntDmndList = [],
|
||||
sttsChgHstryList = [],
|
||||
} = data;
|
||||
|
||||
const editableReviewStep = getEditableReviewStep(
|
||||
userInfo?.dtlInstCd,
|
||||
detDto.prgrsSttsComCd || "",
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="mt-4">
|
||||
<SectionCollapsibleGroup>
|
||||
<div className="sticky top-[6rem] z-30 bg-container px-4 py-2">
|
||||
<SectionCollapsibleToggle />
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<ProgressStatusSection
|
||||
columns={PROGRESS_STATUS_COLUMNS}
|
||||
sttsChgHstryList={sttsChgHstryList}
|
||||
elaprDocNo={detDto.instlSncnDocNo}
|
||||
/>
|
||||
<BasicInfoSection det={detDto} />
|
||||
<ContactInfoSection det={detDto} />
|
||||
<ElaprDocPrgrsStatus elaprDocNo={detDto.instlSncnDocNo} />
|
||||
<BusinessScaleSection bizSclList={bizSclList} />
|
||||
<BusinessCostSection bizCstList={bizCstList} />
|
||||
<ChecklistSection
|
||||
cklstResultList={cklstResultList}
|
||||
prgrsSttsComCd={detDto.prgrsSttsComCd || ""}
|
||||
userRoleCode={userInfo?.dtlInstCd}
|
||||
formState={checklistFormState}
|
||||
setFormState={setChecklistFormState}
|
||||
/>
|
||||
<AttachmentsViewSection
|
||||
esntlFileList={esntlFileList ?? []}
|
||||
etcFileList={etcFileList ?? []}
|
||||
/>
|
||||
<TechReviewOpinionSection
|
||||
ref={techReviewOpinionRef}
|
||||
rvwList={rvwList}
|
||||
editableReviewStep={editableReviewStep}
|
||||
atrzSeComCdList={atrzSeComCdList}
|
||||
isAtrzLoading={needsAtrzResolve && isRvwLoginUserLoading}
|
||||
/>
|
||||
<SupplementDemandSection splmntDmndList={splmntDmndList} />
|
||||
<FloatingActionButtons
|
||||
data={data}
|
||||
docSeComCd="INSTL"
|
||||
instlSncnDocNo={detDto.instlSncnDocNo}
|
||||
prgrsSttsComCd={detDto.prgrsSttsComCd || ""}
|
||||
dtlInstCd={userInfo?.dtlInstCd}
|
||||
atrzSeComCdList={atrzSeComCdList}
|
||||
isRvwLoginUserLoading={needsAtrzResolve && isRvwLoginUserLoading}
|
||||
checklistFormState={checklistFormState}
|
||||
techReviewOpinionRef={techReviewOpinionRef}
|
||||
opnrNm={userInfo?.userName}
|
||||
/>
|
||||
</div>
|
||||
</SectionCollapsibleGroup>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { InstlViewPage };
|
||||
"use client";
|
||||
|
||||
import { useMemo, useRef } from "react";
|
||||
|
||||
import { useUserStore } from "@/shared/lib";
|
||||
import { SectionCollapsibleGroup, SectionCollapsibleToggle } from "@/shared/ui";
|
||||
import { ElaprDocPrgrsStatus } from "@/entities/elapr";
|
||||
import { useInstlSncnDetail } from "@/entities/instl-sncn";
|
||||
import { useRvwMnpwMngLoginUserList } from "@/entities/rvw-mnpw";
|
||||
import {
|
||||
AttachmentsViewSection,
|
||||
ProgressStatusSection,
|
||||
SupplementDemandSection,
|
||||
} from "@/widgets/pbadms-sprt";
|
||||
import { Skeleton } from "@common/components";
|
||||
|
||||
import { needsRoleUser01AtrzResolve } from "../lib/button-config";
|
||||
import { getEditableReviewStep } from "../lib/review-step-utils";
|
||||
import { EMPTY_CKLST_RESULT_LIST, useChecklistFormState } from "../lib/use-checklist-form-state";
|
||||
import { PROGRESS_STATUS_COLUMNS } from "../model/columns";
|
||||
|
||||
import { FloatingActionButtons } from "./components/floating-action-buttons";
|
||||
import { BasicInfoSection } from "./sections/basic-info-section";
|
||||
import { BusinessCostSection } from "./sections/business-cost-section";
|
||||
import { BusinessScaleSection } from "./sections/business-scale-section";
|
||||
import { ContactInfoSection } from "./sections/contact-info-section";
|
||||
import {
|
||||
TechReviewOpinionSection,
|
||||
type TechReviewOpinionSectionHandle,
|
||||
} from "./sections/tech-review-opinion-section";
|
||||
|
||||
interface InstlViewPageProps {
|
||||
id: string;
|
||||
}
|
||||
|
||||
function InstlViewPage({ id }: InstlViewPageProps) {
|
||||
const userInfo = useUserStore((state) => state.userInfo);
|
||||
const techReviewOpinionRef = useRef<TechReviewOpinionSectionHandle>(null);
|
||||
const { data, isLoading, isError } = useInstlSncnDetail(id);
|
||||
const { formState: checklistFormState } = useChecklistFormState(
|
||||
data?.cklstResultList ?? EMPTY_CKLST_RESULT_LIST,
|
||||
);
|
||||
|
||||
const prgrsSttsComCd = data?.detDto?.prgrsSttsComCd || "";
|
||||
const needsAtrzResolve = needsRoleUser01AtrzResolve(userInfo?.dtlInstCd, prgrsSttsComCd);
|
||||
const { data: rvwLoginUserData, isLoading: isRvwLoginUserLoading } = useRvwMnpwMngLoginUserList(
|
||||
{},
|
||||
{ enabled: needsAtrzResolve },
|
||||
);
|
||||
const atrzSeComCdList = useMemo(
|
||||
() => rvwLoginUserData?.content?.map((item) => item.atrzSeComCd) ?? [],
|
||||
[rvwLoginUserData],
|
||||
);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="mt-6 flex flex-col gap-4 px-6">
|
||||
<div className="flex flex-col gap-2">
|
||||
<Skeleton className="h-[4rem] rounded-xl" />
|
||||
<Skeleton className="h-[16rem] rounded-xl" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Skeleton className="h-[4rem] rounded-xl" />
|
||||
<Skeleton className="h-[16rem] rounded-xl" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Skeleton className="h-[4rem] rounded-xl" />
|
||||
<Skeleton className="h-[32rem] rounded-xl" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isError || !data) {
|
||||
return <div className="mt-6 text-[1.4rem]">상세 정보를 찾을 수 없습니다.</div>;
|
||||
}
|
||||
|
||||
const {
|
||||
detDto,
|
||||
bizSclList,
|
||||
bizCstList,
|
||||
esntlFileList,
|
||||
etcFileList,
|
||||
rvwList = [],
|
||||
splmntDmndList = [],
|
||||
sttsChgHstryList = [],
|
||||
} = data;
|
||||
|
||||
const editableReviewStep = getEditableReviewStep(
|
||||
userInfo?.dtlInstCd,
|
||||
detDto.prgrsSttsComCd || "",
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="mt-4">
|
||||
<SectionCollapsibleGroup>
|
||||
<div className="sticky top-[6rem] z-30 bg-container px-4 py-2">
|
||||
<SectionCollapsibleToggle />
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<ProgressStatusSection
|
||||
columns={PROGRESS_STATUS_COLUMNS}
|
||||
sttsChgHstryList={sttsChgHstryList}
|
||||
elaprDocNo={detDto.instlSncnDocNo}
|
||||
/>
|
||||
<BasicInfoSection det={detDto} />
|
||||
<ContactInfoSection det={detDto} />
|
||||
<ElaprDocPrgrsStatus elaprDocNo={detDto.instlSncnDocNo} />
|
||||
<BusinessScaleSection bizSclList={bizSclList} />
|
||||
<BusinessCostSection bizCstList={bizCstList} />
|
||||
<AttachmentsViewSection
|
||||
esntlFileList={esntlFileList ?? []}
|
||||
etcFileList={etcFileList ?? []}
|
||||
/>
|
||||
<TechReviewOpinionSection
|
||||
ref={techReviewOpinionRef}
|
||||
rvwList={rvwList}
|
||||
editableReviewStep={editableReviewStep}
|
||||
atrzSeComCdList={atrzSeComCdList}
|
||||
isAtrzLoading={needsAtrzResolve && isRvwLoginUserLoading}
|
||||
/>
|
||||
<SupplementDemandSection splmntDmndList={splmntDmndList} />
|
||||
<FloatingActionButtons
|
||||
data={data}
|
||||
docSeComCd="INSTL"
|
||||
instlSncnDocNo={detDto.instlSncnDocNo}
|
||||
prgrsSttsComCd={detDto.prgrsSttsComCd || ""}
|
||||
dtlInstCd={userInfo?.dtlInstCd}
|
||||
atrzSeComCdList={atrzSeComCdList}
|
||||
isRvwLoginUserLoading={needsAtrzResolve && isRvwLoginUserLoading}
|
||||
checklistFormState={checklistFormState}
|
||||
techReviewOpinionRef={techReviewOpinionRef}
|
||||
opnrNm={userInfo?.userName}
|
||||
/>
|
||||
</div>
|
||||
</SectionCollapsibleGroup>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { InstlViewPage };
|
||||
|
||||
@@ -8,6 +8,7 @@ import { SectionCollapsible } from "@/shared/ui";
|
||||
import { Field, FieldError, FieldGroup, FieldLabel, FieldSet } from "@/shared/ui/field";
|
||||
import { findCodeLabel, useCodeList } from "@/entities/code";
|
||||
import { type BnsApplyData, BnsPickerDialog } from "@/widgets/bns-picker";
|
||||
import { PreviousPlanLookupButton } from "@/widgets/previous-plan-lookup";
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
@@ -27,9 +28,9 @@ function BasicInfoFormSection() {
|
||||
const { control, watch, setValue } = useFormContext<InstlSncnRegistFormData>();
|
||||
const [isPickerOpen, setIsPickerOpen] = useState(false);
|
||||
const taskSeComCd = watch("instlSncnMng.taskSeComCd");
|
||||
const localGovLabel = [watch("instlSncnMng.sido"), watch("instlSncnMng.gugun")]
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
const sido = watch("instlSncnMng.sido");
|
||||
const gugun = watch("instlSncnMng.gugun");
|
||||
const localGovLabel = [sido, gugun].filter(Boolean).join(" ");
|
||||
|
||||
function handleBnsSelect(data: BnsApplyData) {
|
||||
setValue("instlSncnMng.bizNm", data.bizNm);
|
||||
@@ -53,6 +54,7 @@ function BasicInfoFormSection() {
|
||||
|
||||
return (
|
||||
<SectionCollapsible title="기본 정보">
|
||||
<PreviousPlanLookupButton sidoNm={sido} gugunNm={gugun} />
|
||||
<Table caption="기본 정보 입력으로 사업명, 사업기간, 사업내용, 구분, 신청 지자체, 사업구분, 시설종류, 재이용용도 정보 제공" variant="secondary">
|
||||
<colgroup>
|
||||
<col width="15%" />
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { SectionCollapsible } from "@/shared/ui";
|
||||
import type { InstlSncnMngResponseDto } from "@/entities/instl-sncn";
|
||||
import { PreviousPlanLookupButton } from "@/widgets/previous-plan-lookup";
|
||||
import { Table, TableCell, TableHead, TableHeader, TableRow } from "@common/components";
|
||||
|
||||
interface BasicInfoSectionProps {
|
||||
@@ -11,6 +12,7 @@ interface BasicInfoSectionProps {
|
||||
function BasicInfoSection({ det }: BasicInfoSectionProps) {
|
||||
return (
|
||||
<SectionCollapsible title="기본 정보">
|
||||
<PreviousPlanLookupButton sidoNm={det.sido} gugunNm={det.gugun} />
|
||||
<Table caption="기본 정보로 사업명, 사업기간, 사업내용, 구분, 신청지자체, 사업구분 정보 제공" variant="secondary">
|
||||
<colgroup>
|
||||
<col width="15%" />
|
||||
|
||||
@@ -1,350 +1,350 @@
|
||||
"use client";
|
||||
|
||||
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
|
||||
|
||||
import { dayjs, getDefaultFileKey, useTus } from "@/shared/lib";
|
||||
import { TextShimmer } from "@/shared/ui";
|
||||
import { SectionCollapsible } from "@/shared/ui";
|
||||
import { Field, FieldLabel, FieldSet } from "@/shared/ui/field";
|
||||
import { AttachmentDownloadLink, attachService } from "@/entities/attach";
|
||||
import type { InstlSncnRvwOpnnResponseDto, ReviewStepCode } from "@/entities/instl-sncn";
|
||||
import {
|
||||
Button,
|
||||
RadioGroup,
|
||||
RadioGroupItem,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
Textarea,
|
||||
} from "@common/components";
|
||||
|
||||
import type { ReviewOpinionFormData } from "../../lib/review-opinion-utils";
|
||||
import { REVIEW_STEP_LABELS } from "../../model/view-types";
|
||||
|
||||
const RVW_OPNN_FILE_KEY_PREFIX = "rvw-opnn::";
|
||||
|
||||
interface TechReviewOpinionSectionProps {
|
||||
rvwList: InstlSncnRvwOpnnResponseDto[];
|
||||
editableReviewStep?: ReviewStepCode;
|
||||
atrzSeComCdList?: string[];
|
||||
isAtrzLoading?: boolean;
|
||||
}
|
||||
|
||||
export interface TechReviewOpinionSectionHandle {
|
||||
getFormData: () => ReviewOpinionFormData | null;
|
||||
resetForm: () => void;
|
||||
}
|
||||
|
||||
interface TechReviewOpinionTableProps {
|
||||
items: InstlSncnRvwOpnnResponseDto[];
|
||||
}
|
||||
|
||||
interface UploadedOpinionFile {
|
||||
fileName: string;
|
||||
fileKey: string;
|
||||
atflId?: string;
|
||||
atflCd?: string;
|
||||
}
|
||||
|
||||
const EMPTY_FORM_DATA: ReviewOpinionFormData = {
|
||||
rvwRsltCn: "",
|
||||
rvwRsltAnmlyYn: "",
|
||||
atchFileGroupId: "",
|
||||
opnrSeComCd: "",
|
||||
};
|
||||
|
||||
function createRvwOpnnUploadKey(file: File) {
|
||||
return `${RVW_OPNN_FILE_KEY_PREFIX}${getDefaultFileKey(file)}`;
|
||||
}
|
||||
|
||||
function hasFormContent(formData: ReviewOpinionFormData) {
|
||||
return Boolean(formData.rvwRsltCn.trim() || formData.rvwRsltAnmlyYn || formData.atchFileGroupId);
|
||||
}
|
||||
|
||||
const TechReviewOpinionSection = forwardRef<
|
||||
TechReviewOpinionSectionHandle,
|
||||
TechReviewOpinionSectionProps
|
||||
>(function TechReviewOpinionSection(
|
||||
{ rvwList, editableReviewStep, atrzSeComCdList = [], isAtrzLoading = false },
|
||||
ref,
|
||||
) {
|
||||
const canWriteKecoOpinion = !isAtrzLoading && atrzSeComCdList.includes("ATRZ02");
|
||||
const [formData, setFormData] = useState<ReviewOpinionFormData>({
|
||||
...EMPTY_FORM_DATA,
|
||||
opnrSeComCd: editableReviewStep ?? "",
|
||||
});
|
||||
const [uploadedFile, setUploadedFile] = useState<UploadedOpinionFile | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
opnrSeComCd: editableReviewStep ?? "",
|
||||
}));
|
||||
}, [editableReviewStep]);
|
||||
|
||||
const { uploadFileAsync, isUploading, uploads } = useTus({
|
||||
onSuccess: (atflCd, atflId, _file, fileKey) => {
|
||||
if (!atflCd || !atflId) return;
|
||||
|
||||
setFormData((prev) => ({ ...prev, atchFileGroupId: atflCd }));
|
||||
setUploadedFile((prev) => (prev?.fileKey === fileKey ? { ...prev, atflCd, atflId } : prev));
|
||||
},
|
||||
onError: (_error, _file, fileKey) => {
|
||||
setUploadedFile((prev) => (prev?.fileKey === fileKey ? null : prev));
|
||||
},
|
||||
});
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
getFormData: () => {
|
||||
if (!editableReviewStep || !hasFormContent(formData)) {
|
||||
return null;
|
||||
}
|
||||
if (editableReviewStep === "KECO" && !canWriteKecoOpinion) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...formData,
|
||||
opnrSeComCd: editableReviewStep,
|
||||
};
|
||||
},
|
||||
resetForm: () => {
|
||||
setFormData({
|
||||
...EMPTY_FORM_DATA,
|
||||
opnrSeComCd: editableReviewStep ?? "",
|
||||
});
|
||||
setUploadedFile(null);
|
||||
},
|
||||
}),
|
||||
[canWriteKecoOpinion, editableReviewStep, formData],
|
||||
);
|
||||
|
||||
const envRvwList = rvwList.filter((item) => item.opnrSeComCd === "ENV");
|
||||
const kecoRvwList = rvwList.filter((item) => item.opnrSeComCd === "KECO");
|
||||
|
||||
const hasEnvSection = envRvwList.length > 0 || editableReviewStep === "ENV";
|
||||
const hasKecoSection =
|
||||
kecoRvwList.length > 0 || (editableReviewStep === "KECO" && canWriteKecoOpinion);
|
||||
|
||||
if (!hasEnvSection && !hasKecoSection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
async function handleFileUpload(files: FileList | null) {
|
||||
if (!files || files.length === 0) return;
|
||||
|
||||
const file = files[0];
|
||||
const fileKey = createRvwOpnnUploadKey(file);
|
||||
const existingAtflCd = formData.atchFileGroupId;
|
||||
|
||||
setUploadedFile({ fileName: file.name, fileKey });
|
||||
|
||||
try {
|
||||
await uploadFileAsync(file, {
|
||||
fileKey,
|
||||
metadata: existingAtflCd ? { atflCd: existingAtflCd } : undefined,
|
||||
});
|
||||
} catch {
|
||||
// onError callback handles cleanup
|
||||
}
|
||||
}
|
||||
|
||||
async function handleFileRemove() {
|
||||
if (!uploadedFile) return;
|
||||
|
||||
uploads[uploadedFile.fileKey]?.abort();
|
||||
|
||||
if (uploadedFile.atflId) {
|
||||
try {
|
||||
await attachService.deleteAttachmentByAtflId(uploadedFile.atflId);
|
||||
} catch (error) {
|
||||
console.error("검토의견 첨부파일 삭제 실패:", error);
|
||||
}
|
||||
}
|
||||
|
||||
setUploadedFile(null);
|
||||
setFormData((prev) => ({ ...prev, atchFileGroupId: "" }));
|
||||
}
|
||||
|
||||
function renderOpinionForm(reviewStep: ReviewStepCode) {
|
||||
if (editableReviewStep !== reviewStep) return null;
|
||||
if (reviewStep === "KECO" && !canWriteKecoOpinion) return null;
|
||||
|
||||
const uploading = uploadedFile ? (isUploading[uploadedFile.fileKey] ?? false) : false;
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-xl font-semibold">검토의견 작성</h4>
|
||||
<Table caption="검토의견 작성으로 의견내용, 검토결과, 첨부파일 정보 제공" variant="secondary">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead scope="row" className="w-[12rem]">의견내용</TableHead>
|
||||
<TableCell>
|
||||
<Textarea
|
||||
value={formData.rvwRsltCn}
|
||||
onChange={(event) =>
|
||||
setFormData((prev) => ({ ...prev, rvwRsltCn: event.target.value }))
|
||||
}
|
||||
placeholder="의견내용을 입력하세요"
|
||||
rows={4}
|
||||
className="min-h-[8rem] !text-xl"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead scope="row">검토결과</TableHead>
|
||||
<TableCell>
|
||||
<FieldSet>
|
||||
<RadioGroup
|
||||
value={formData.rvwRsltAnmlyYn}
|
||||
onValueChange={(value) =>
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
rvwRsltAnmlyYn: value as ReviewOpinionFormData["rvwRsltAnmlyYn"],
|
||||
}))
|
||||
}
|
||||
>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<FieldLabel
|
||||
htmlFor={`rvw-opnn-normal-${reviewStep}`}
|
||||
className="!w-auto border-none"
|
||||
>
|
||||
<Field orientation="horizontal" className="w-auto">
|
||||
<RadioGroupItem value="N" id={`rvw-opnn-normal-${reviewStep}`} />
|
||||
<span className="font-normal">정상</span>
|
||||
</Field>
|
||||
</FieldLabel>
|
||||
<FieldLabel
|
||||
htmlFor={`rvw-opnn-abnormal-${reviewStep}`}
|
||||
className="!w-auto border-none"
|
||||
>
|
||||
<Field orientation="horizontal" className="w-auto">
|
||||
<RadioGroupItem value="Y" id={`rvw-opnn-abnormal-${reviewStep}`} />
|
||||
<span className="font-normal">이상</span>
|
||||
</Field>
|
||||
</FieldLabel>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</FieldSet>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead scope="row">첨부파일</TableHead>
|
||||
<TableCell>
|
||||
{uploadedFile ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<TextShimmer className="text-xl" active={uploading}>
|
||||
{uploadedFile.fileName}
|
||||
</TextShimmer>
|
||||
<Button
|
||||
size="icon"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
void handleFileRemove();
|
||||
}}
|
||||
disabled={uploading}
|
||||
className="size-8 !bg-destructive"
|
||||
>
|
||||
<i className="diveicon di-x text-xs" aria-label="삭제" />
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<label>
|
||||
<input
|
||||
type="file"
|
||||
className="hidden"
|
||||
onChange={(event) => {
|
||||
void handleFileUpload(event.target.files);
|
||||
event.target.value = "";
|
||||
}}
|
||||
/>
|
||||
<Button size="sm" variant="outline" type="button" asChild>
|
||||
<span>파일 선택</span>
|
||||
</Button>
|
||||
</label>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{hasEnvSection ? (
|
||||
<SectionCollapsible title={`${REVIEW_STEP_LABELS.ENV} 검토의견`}>
|
||||
<div className="space-y-6">
|
||||
{envRvwList.length > 0 ? <TechReviewOpinionTable items={envRvwList} /> : null}
|
||||
{renderOpinionForm("ENV")}
|
||||
</div>
|
||||
</SectionCollapsible>
|
||||
) : null}
|
||||
{hasKecoSection ? (
|
||||
<SectionCollapsible title={`${REVIEW_STEP_LABELS.KECO} 검토의견`}>
|
||||
<div className="space-y-6">
|
||||
{kecoRvwList.length > 0 ? <TechReviewOpinionTable items={kecoRvwList} /> : null}
|
||||
{renderOpinionForm("KECO")}
|
||||
</div>
|
||||
</SectionCollapsible>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
function TechReviewOpinionTable({ items }: TechReviewOpinionTableProps) {
|
||||
return (
|
||||
<div className="overflow-x-auto">
|
||||
<Table caption="검토의견 목록으로 의견자명, 의견내용, 검토결과, 첨부파일, 등록일시 정보 제공" variant="secondary">
|
||||
<colgroup>
|
||||
<col width="10%" />
|
||||
<col width="55%" />
|
||||
<col width="5%" />
|
||||
<col width="20%" />
|
||||
<col width="10%" />
|
||||
</colgroup>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>의견자명</TableHead>
|
||||
<TableHead>의견내용</TableHead>
|
||||
<TableHead>검토결과</TableHead>
|
||||
<TableHead>첨부파일</TableHead>
|
||||
<TableHead>등록일시</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{items.map((item) => (
|
||||
<TableRow key={`${item.instlSncnDocNo}_${item.rvwOpnnSn}`}>
|
||||
<TableCell align="center">{item.opnrNm}</TableCell>
|
||||
<TableCell align="left">{item.rvwRsltCn || "-"}</TableCell>
|
||||
<TableCell align="center">
|
||||
{item.rvwRsltAnmlyYn === "Y" ? "이상" : item.rvwRsltAnmlyYn === "N" ? "정상" : "-"}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
{item.atchFileInfo?.atflCd ? (
|
||||
<AttachmentDownloadLink
|
||||
atflCd={item.atchFileInfo.atflCd}
|
||||
fileName={item.atchFileInfo.orgnlFileNm || "다운로드"}
|
||||
/>
|
||||
) : (
|
||||
"-"
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
{item.regDt ? dayjs(item.regDt).format("YYYY-MM-DD HH:mm:ss") : "-"}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { TechReviewOpinionSection };
|
||||
"use client";
|
||||
|
||||
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
|
||||
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { getDefaultFileKey, useTus, useUserStore } from "@/shared/lib";
|
||||
import { TextShimmer } from "@/shared/ui";
|
||||
import { SectionCollapsible } from "@/shared/ui";
|
||||
import { Field, FieldLabel, FieldSet } from "@/shared/ui/field";
|
||||
import { attachService } from "@/entities/attach";
|
||||
import {
|
||||
instlSncnKeys,
|
||||
type InstlSncnRvwOpnnResponseDto,
|
||||
instlSncnRvwOpnnService,
|
||||
type ReviewStepCode,
|
||||
} from "@/entities/instl-sncn";
|
||||
import { RvwOpnnListTable } from "@/widgets/pbadms-sprt";
|
||||
import {
|
||||
Button,
|
||||
RadioGroup,
|
||||
RadioGroupItem,
|
||||
Table,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
Textarea,
|
||||
} from "@common/components";
|
||||
|
||||
import type { ReviewOpinionFormData } from "../../lib/review-opinion-utils";
|
||||
import { REVIEW_STEP_LABELS } from "../../model/view-types";
|
||||
|
||||
const RVW_OPNN_FILE_KEY_PREFIX = "rvw-opnn::";
|
||||
|
||||
interface TechReviewOpinionSectionProps {
|
||||
rvwList: InstlSncnRvwOpnnResponseDto[];
|
||||
editableReviewStep?: ReviewStepCode;
|
||||
atrzSeComCdList?: string[];
|
||||
isAtrzLoading?: boolean;
|
||||
}
|
||||
|
||||
export interface TechReviewOpinionSectionHandle {
|
||||
getFormData: () => ReviewOpinionFormData | null;
|
||||
resetForm: () => void;
|
||||
}
|
||||
|
||||
interface TechReviewOpinionTableProps {
|
||||
items: InstlSncnRvwOpnnResponseDto[];
|
||||
}
|
||||
|
||||
interface UploadedOpinionFile {
|
||||
fileName: string;
|
||||
fileKey: string;
|
||||
atflId?: string;
|
||||
atflCd?: string;
|
||||
}
|
||||
|
||||
const EMPTY_FORM_DATA: ReviewOpinionFormData = {
|
||||
rvwRsltCn: "",
|
||||
rvwRsltAnmlyYn: "",
|
||||
atchFileGroupId: "",
|
||||
opnrSeComCd: "",
|
||||
};
|
||||
|
||||
function createRvwOpnnUploadKey(file: File) {
|
||||
return `${RVW_OPNN_FILE_KEY_PREFIX}${getDefaultFileKey(file)}`;
|
||||
}
|
||||
|
||||
function hasFormContent(formData: ReviewOpinionFormData) {
|
||||
return Boolean(formData.rvwRsltCn.trim() || formData.rvwRsltAnmlyYn || formData.atchFileGroupId);
|
||||
}
|
||||
|
||||
const TechReviewOpinionSection = forwardRef<
|
||||
TechReviewOpinionSectionHandle,
|
||||
TechReviewOpinionSectionProps
|
||||
>(function TechReviewOpinionSection(
|
||||
{ rvwList, editableReviewStep, atrzSeComCdList = [], isAtrzLoading = false },
|
||||
ref,
|
||||
) {
|
||||
const canWriteKecoOpinion = !isAtrzLoading && atrzSeComCdList.includes("ATRZ02");
|
||||
const [formData, setFormData] = useState<ReviewOpinionFormData>({
|
||||
...EMPTY_FORM_DATA,
|
||||
opnrSeComCd: editableReviewStep ?? "",
|
||||
});
|
||||
const [uploadedFile, setUploadedFile] = useState<UploadedOpinionFile | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
opnrSeComCd: editableReviewStep ?? "",
|
||||
}));
|
||||
}, [editableReviewStep]);
|
||||
|
||||
const { uploadFileAsync, isUploading, uploads } = useTus({
|
||||
onSuccess: (atflCd, atflId, _file, fileKey) => {
|
||||
if (!atflCd || !atflId) return;
|
||||
|
||||
setFormData((prev) => ({ ...prev, atchFileGroupId: atflCd }));
|
||||
setUploadedFile((prev) => (prev?.fileKey === fileKey ? { ...prev, atflCd, atflId } : prev));
|
||||
},
|
||||
onError: (_error, _file, fileKey) => {
|
||||
setUploadedFile((prev) => (prev?.fileKey === fileKey ? null : prev));
|
||||
},
|
||||
});
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
getFormData: () => {
|
||||
if (!editableReviewStep || !hasFormContent(formData)) {
|
||||
return null;
|
||||
}
|
||||
if (editableReviewStep === "KECO" && !canWriteKecoOpinion) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...formData,
|
||||
opnrSeComCd: editableReviewStep,
|
||||
};
|
||||
},
|
||||
resetForm: () => {
|
||||
setFormData({
|
||||
...EMPTY_FORM_DATA,
|
||||
opnrSeComCd: editableReviewStep ?? "",
|
||||
});
|
||||
setUploadedFile(null);
|
||||
},
|
||||
}),
|
||||
[canWriteKecoOpinion, editableReviewStep, formData],
|
||||
);
|
||||
|
||||
const envRvwList = rvwList.filter((item) => item.opnrSeComCd === "ENV");
|
||||
const kecoRvwList = rvwList.filter((item) => item.opnrSeComCd === "KECO");
|
||||
|
||||
const hasEnvSection = envRvwList.length > 0 || editableReviewStep === "ENV";
|
||||
const hasKecoSection =
|
||||
kecoRvwList.length > 0 || (editableReviewStep === "KECO" && canWriteKecoOpinion);
|
||||
|
||||
if (!hasEnvSection && !hasKecoSection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
async function handleFileUpload(files: FileList | null) {
|
||||
if (!files || files.length === 0) return;
|
||||
|
||||
const file = files[0];
|
||||
const fileKey = createRvwOpnnUploadKey(file);
|
||||
const existingAtflCd = formData.atchFileGroupId;
|
||||
|
||||
setUploadedFile({ fileName: file.name, fileKey });
|
||||
|
||||
try {
|
||||
await uploadFileAsync(file, {
|
||||
fileKey,
|
||||
metadata: existingAtflCd ? { atflCd: existingAtflCd } : undefined,
|
||||
});
|
||||
} catch {
|
||||
// onError callback handles cleanup
|
||||
}
|
||||
}
|
||||
|
||||
async function handleFileRemove() {
|
||||
if (!uploadedFile) return;
|
||||
|
||||
uploads[uploadedFile.fileKey]?.abort();
|
||||
|
||||
if (uploadedFile.atflId) {
|
||||
try {
|
||||
await attachService.deleteAttachmentByAtflId(uploadedFile.atflId);
|
||||
} catch (error) {
|
||||
console.error("검토의견 첨부파일 삭제 실패:", error);
|
||||
}
|
||||
}
|
||||
|
||||
setUploadedFile(null);
|
||||
setFormData((prev) => ({ ...prev, atchFileGroupId: "" }));
|
||||
}
|
||||
|
||||
function isReviewTurn(reviewStep: ReviewStepCode) {
|
||||
if (editableReviewStep !== reviewStep) return false;
|
||||
if (reviewStep === "KECO" && !canWriteKecoOpinion) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function renderOpinionForm(reviewStep: ReviewStepCode) {
|
||||
if (!isReviewTurn(reviewStep)) return null;
|
||||
|
||||
const uploading = uploadedFile ? (isUploading[uploadedFile.fileKey] ?? false) : false;
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-xl font-semibold">검토의견 작성</h4>
|
||||
<Table
|
||||
caption="검토의견 작성으로 의견내용, 검토결과, 첨부파일 정보 제공"
|
||||
variant="secondary"
|
||||
>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead scope="row" className="w-[12rem]">
|
||||
의견내용
|
||||
</TableHead>
|
||||
<TableCell>
|
||||
<Textarea
|
||||
value={formData.rvwRsltCn}
|
||||
onChange={(event) =>
|
||||
setFormData((prev) => ({ ...prev, rvwRsltCn: event.target.value }))
|
||||
}
|
||||
placeholder="의견내용을 입력하세요"
|
||||
rows={4}
|
||||
className="min-h-[8rem] !text-xl"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead scope="row">검토결과</TableHead>
|
||||
<TableCell>
|
||||
<FieldSet>
|
||||
<RadioGroup
|
||||
value={formData.rvwRsltAnmlyYn}
|
||||
onValueChange={(value) =>
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
rvwRsltAnmlyYn: value as ReviewOpinionFormData["rvwRsltAnmlyYn"],
|
||||
}))
|
||||
}
|
||||
>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<FieldLabel
|
||||
htmlFor={`rvw-opnn-normal-${reviewStep}`}
|
||||
className="!w-auto border-none"
|
||||
>
|
||||
<Field orientation="horizontal" className="w-auto">
|
||||
<RadioGroupItem value="N" id={`rvw-opnn-normal-${reviewStep}`} />
|
||||
<span className="font-normal">정상</span>
|
||||
</Field>
|
||||
</FieldLabel>
|
||||
<FieldLabel
|
||||
htmlFor={`rvw-opnn-abnormal-${reviewStep}`}
|
||||
className="!w-auto border-none"
|
||||
>
|
||||
<Field orientation="horizontal" className="w-auto">
|
||||
<RadioGroupItem value="Y" id={`rvw-opnn-abnormal-${reviewStep}`} />
|
||||
<span className="font-normal">이상</span>
|
||||
</Field>
|
||||
</FieldLabel>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</FieldSet>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead scope="row">첨부파일</TableHead>
|
||||
<TableCell>
|
||||
{uploadedFile ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<TextShimmer className="text-xl" active={uploading}>
|
||||
{uploadedFile.fileName}
|
||||
</TextShimmer>
|
||||
<Button
|
||||
size="icon"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
void handleFileRemove();
|
||||
}}
|
||||
disabled={uploading}
|
||||
className="size-8 !bg-destructive"
|
||||
>
|
||||
<i className="diveicon di-x text-xs" aria-label="삭제" />
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<label>
|
||||
<input
|
||||
type="file"
|
||||
className="hidden"
|
||||
onChange={(event) => {
|
||||
void handleFileUpload(event.target.files);
|
||||
event.target.value = "";
|
||||
}}
|
||||
/>
|
||||
<Button size="sm" variant="outline" type="button" asChild>
|
||||
<span>파일 선택</span>
|
||||
</Button>
|
||||
</label>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{hasEnvSection ? (
|
||||
<SectionCollapsible title={`${REVIEW_STEP_LABELS.ENV} 검토의견`}>
|
||||
<div className="space-y-6">
|
||||
{envRvwList.length > 0 ? (
|
||||
<TechReviewOpinionTable items={envRvwList} />
|
||||
) : null}
|
||||
{renderOpinionForm("ENV")}
|
||||
</div>
|
||||
</SectionCollapsible>
|
||||
) : null}
|
||||
{hasKecoSection ? (
|
||||
<SectionCollapsible title={`${REVIEW_STEP_LABELS.KECO} 검토의견`}>
|
||||
<div className="space-y-6">
|
||||
{kecoRvwList.length > 0 ? (
|
||||
<TechReviewOpinionTable items={kecoRvwList} />
|
||||
) : null}
|
||||
{renderOpinionForm("KECO")}
|
||||
</div>
|
||||
</SectionCollapsible>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
function getRvwOpnnIds(item: InstlSncnRvwOpnnResponseDto) {
|
||||
if (!item.instlSncnDocNo || item.rvwOpnnSn == null) {
|
||||
throw new Error("검토의견 식별값이 없습니다.");
|
||||
}
|
||||
return [item.instlSncnDocNo, item.rvwOpnnSn] as const;
|
||||
}
|
||||
|
||||
function TechReviewOpinionTable({ items }: TechReviewOpinionTableProps) {
|
||||
const queryClient = useQueryClient();
|
||||
const currentUserUuid = useUserStore((state) => state.userInfo?.userUnqId);
|
||||
|
||||
return (
|
||||
<RvwOpnnListTable
|
||||
items={items}
|
||||
getRowKey={(item) => `${item.instlSncnDocNo}_${item.rvwOpnnSn}`}
|
||||
currentUserUuid={currentUserUuid}
|
||||
onUpdate={async (item, body) => {
|
||||
await instlSncnRvwOpnnService.update(...getRvwOpnnIds(item), body);
|
||||
await queryClient.invalidateQueries({ queryKey: instlSncnKeys.all });
|
||||
}}
|
||||
onDelete={async (item) => {
|
||||
await instlSncnRvwOpnnService.delete(...getRvwOpnnIds(item));
|
||||
await queryClient.invalidateQueries({ queryKey: instlSncnKeys.all });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export { TechReviewOpinionSection };
|
||||
|
||||
@@ -89,8 +89,8 @@ function MyPbadmsSprtListPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4 px-[1.8rem] pb-6">
|
||||
<section className="card card-border mx-0 bg-background">
|
||||
<div className="card card-border !mt-6">
|
||||
<div className="card card-border">
|
||||
<form className="flex flex-wrap items-center gap-4 p-2" onSubmit={handleSearch}>
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="whitespace-nowrap">기간</Label>
|
||||
@@ -157,7 +157,7 @@ function MyPbadmsSprtListPage() {
|
||||
|
||||
<Button type="submit">검색</Button>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<MyPbadmsSprtList
|
||||
searchParams={searchParams}
|
||||
|
||||
@@ -77,12 +77,12 @@ function MyPbadmsSprtList({ searchParams, pagination, onPaginationChange }: MyPb
|
||||
});
|
||||
|
||||
return (
|
||||
<section className="card card-border mx-0 bg-background pb-8 pt-0">
|
||||
<div className="card card-border pt-0">
|
||||
<div className="card-header flex items-center justify-between">
|
||||
<h3 className="text-2xl font-semibold">내 신청 목록</h3>
|
||||
<h3 className="text-2xl font-medium text-label">내 신청 목록</h3>
|
||||
</div>
|
||||
{tableComponent}
|
||||
</section>
|
||||
<div className="card !m-[1.2rem] !p-0">{tableComponent}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -71,8 +71,8 @@ function NtiHstryListPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4 px-[1.8rem] pb-6">
|
||||
<section className="card card-border mx-0 bg-background">
|
||||
<div className="card card-border !mt-6">
|
||||
<div className="card card-border">
|
||||
<form className="flex flex-wrap items-center gap-4 p-2" onSubmit={handleSearch}>
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="whitespace-nowrap">기간</Label>
|
||||
@@ -114,7 +114,7 @@ function NtiHstryListPage() {
|
||||
|
||||
<Button type="submit">검색</Button>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<NtiHstryList
|
||||
searchParams={searchParams}
|
||||
|
||||
@@ -103,12 +103,12 @@ function NtiHstryList({ searchParams, pagination, onPaginationChange }: NtiHstry
|
||||
});
|
||||
|
||||
return (
|
||||
<section className="card card-border mx-0 bg-background pb-8 pt-0">
|
||||
<div className="card card-border pt-0">
|
||||
<div className="card-header flex items-center justify-between">
|
||||
<h3 className="text-2xl font-semibold">알림 이력 목록</h3>
|
||||
<h3 className="text-2xl font-medium text-label">알림 이력 목록</h3>
|
||||
</div>
|
||||
{tableComponent}
|
||||
</section>
|
||||
<div className="card !m-[1.2rem] !p-0">{tableComponent}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,221 +1,235 @@
|
||||
"use client";
|
||||
|
||||
import { Fragment } from "react";
|
||||
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
|
||||
import { dayjs } from "@/shared/lib";
|
||||
import type { SewerBscPlanResponseDto } from "@/entities/sewer-bsc-plan";
|
||||
import { Badge } from "@common/components";
|
||||
|
||||
const columnHelper = createColumnHelper<SewerBscPlanResponseDto>();
|
||||
|
||||
const MAIN_STEP_LABELS = {
|
||||
apply: "기본계획 신청",
|
||||
envoReview: "기본계획 검토",
|
||||
techReview: "기술검토",
|
||||
reply: "검토결과 회신",
|
||||
} as const;
|
||||
|
||||
export const PROGRESS_STATUS_COLUMNS = [
|
||||
{ label: "기본계획 신청 (시군)", sttsComCd: "LE" },
|
||||
{ label: "기본계획 검토", sttsComCd: "ER" },
|
||||
{ label: "기술검토", sttsComCd: "TE" },
|
||||
{ label: "기술검토 회신", sttsComCd: "TP" },
|
||||
{ label: "횐경청 검토 결과 회신", sttsComCd: "EP" },
|
||||
];
|
||||
|
||||
type BadgeState = "completed" | "current" | "pending";
|
||||
|
||||
interface ProgressBadge {
|
||||
label: string;
|
||||
state: BadgeState;
|
||||
}
|
||||
|
||||
function buildProgressBadges(prgrsSttsComCd: string, techRplyHstryYn: "Y" | "N"): ProgressBadge[] {
|
||||
if (prgrsSttsComCd === "SV") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
if (prgrsSttsComCd === "ST") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "current" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
if (prgrsSttsComCd === "LE") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "current" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
if (prgrsSttsComCd === "ER" || prgrsSttsComCd === "TE" || prgrsSttsComCd === "SL") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "current" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
if (prgrsSttsComCd === "TP") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "completed" },
|
||||
{
|
||||
label: MAIN_STEP_LABELS.techReview,
|
||||
state: techRplyHstryYn === "Y" ? "completed" : "pending",
|
||||
},
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "current" },
|
||||
];
|
||||
}
|
||||
|
||||
if (prgrsSttsComCd === "EP") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "completed" },
|
||||
{
|
||||
label: MAIN_STEP_LABELS.techReview,
|
||||
state: techRplyHstryYn === "Y" ? "completed" : "pending",
|
||||
},
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "completed" },
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
function getProgressStatusBadge(prgrsSttsComCd?: string | null) {
|
||||
if (prgrsSttsComCd === "SL") {
|
||||
return <Badge color="orange">보완요청</Badge>;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getBadgeColor(state: BadgeState) {
|
||||
switch (state) {
|
||||
case "completed":
|
||||
return "green";
|
||||
case "current":
|
||||
return "blue";
|
||||
case "pending":
|
||||
return "lightGray";
|
||||
}
|
||||
}
|
||||
|
||||
function getBadgeStateText(state: BadgeState) {
|
||||
switch (state) {
|
||||
case "completed":
|
||||
return "완료";
|
||||
case "current":
|
||||
return "진행중";
|
||||
case "pending":
|
||||
return "대기";
|
||||
}
|
||||
}
|
||||
|
||||
function formatOptional(value: string | undefined) {
|
||||
return value?.trim() ? value : "-";
|
||||
}
|
||||
|
||||
interface ProgressStepBadgeProps {
|
||||
prgrsSttsComCd: string;
|
||||
techRplyHstryYn: "Y" | "N";
|
||||
}
|
||||
|
||||
function ProgressStepBadge({ prgrsSttsComCd, techRplyHstryYn }: ProgressStepBadgeProps) {
|
||||
const badges = buildProgressBadges(prgrsSttsComCd, techRplyHstryYn);
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-center justify-start">
|
||||
{badges.map((badge, index) => (
|
||||
<Fragment key={`${badge.label}-${index}`}>
|
||||
{index > 0 && <span className="mx-2">></span>}
|
||||
<Badge color={getBadgeColor(badge.state)}>
|
||||
{badge.label}({getBadgeStateText(badge.state)})
|
||||
</Badge>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function createSewerBscPlanColumns() {
|
||||
return [
|
||||
columnHelper.accessor("sido", {
|
||||
header: "시도",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
}),
|
||||
columnHelper.accessor("gugun", {
|
||||
header: "시군구",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
}),
|
||||
columnHelper.accessor("sewerBscPlanNm", {
|
||||
header: "기본계획명",
|
||||
meta: { align: "left" },
|
||||
cell: ({ row, getValue }) => (
|
||||
<>
|
||||
{getProgressStatusBadge(row.original.prgrsSttsComCd)} {formatOptional(getValue())}
|
||||
</>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("sewerBscPlanSeCdNm", {
|
||||
header: "계획구분",
|
||||
meta: { align: "center" },
|
||||
cell: ({ row }) =>
|
||||
formatOptional(row.original.sewerBscPlanSeCdNm || row.original.sewerBscPlanSeComCd),
|
||||
}),
|
||||
columnHelper.accessor("taskSeCdNm", {
|
||||
header: "업무구분",
|
||||
meta: { align: "center" },
|
||||
cell: ({ row }) => formatOptional(row.original.taskSeCdNm || row.original.taskSeComCd),
|
||||
}),
|
||||
columnHelper.accessor("rgtrNm", {
|
||||
header: "담당자",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
}),
|
||||
columnHelper.accessor("envoSeCdNm", {
|
||||
header: "해당기관",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: "progress",
|
||||
header: "진행상황",
|
||||
meta: { align: "center" },
|
||||
cell: ({ row }) => (
|
||||
<ProgressStepBadge
|
||||
prgrsSttsComCd={row.original.prgrsSttsComCd || ""}
|
||||
techRplyHstryYn={row.original.techRplyHstryYn || "N"}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("regDt", {
|
||||
header: "등록일",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => {
|
||||
const value = getValue();
|
||||
return value ? dayjs(value).format("YYYY-MM-DD") : "-";
|
||||
},
|
||||
}),
|
||||
];
|
||||
}
|
||||
"use client";
|
||||
|
||||
import { Fragment } from "react";
|
||||
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
|
||||
import { dayjs } from "@/shared/lib";
|
||||
import type { SewerBscPlanResponseDto } from "@/entities/sewer-bsc-plan";
|
||||
import { Badge } from "@common/components";
|
||||
|
||||
const columnHelper = createColumnHelper<SewerBscPlanResponseDto>();
|
||||
|
||||
const MAIN_STEP_LABELS = {
|
||||
apply: "기본계획 신청",
|
||||
envoReview: "기본계획 검토",
|
||||
techReview: "기술검토",
|
||||
reply: "검토결과 회신",
|
||||
} as const;
|
||||
|
||||
export const PROGRESS_STATUS_COLUMNS = [
|
||||
{ label: "기본계획 신청 (시군)", sttsComCd: "LE" },
|
||||
{ label: "기본계획 검토", sttsComCd: "ER" },
|
||||
{ label: "기술검토", sttsComCd: "TE" },
|
||||
{ label: "기술검토 회신", sttsComCd: "TP" },
|
||||
{ label: "횐경청 검토 결과 회신", sttsComCd: "EP" },
|
||||
];
|
||||
|
||||
type BadgeState = "completed" | "current" | "pending";
|
||||
|
||||
interface ProgressBadge {
|
||||
label: string;
|
||||
state: BadgeState;
|
||||
}
|
||||
|
||||
function buildProgressBadges(prgrsSttsComCd: string, techRplyHstryYn: "Y" | "N"): ProgressBadge[] {
|
||||
if (prgrsSttsComCd === "SV") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
if (prgrsSttsComCd === "ST") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "current" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
if (prgrsSttsComCd === "LE") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "current" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
if (prgrsSttsComCd === "ER" || prgrsSttsComCd === "TE" || prgrsSttsComCd === "SL") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "current" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
if (prgrsSttsComCd === "TP") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "completed" },
|
||||
{
|
||||
label: MAIN_STEP_LABELS.techReview,
|
||||
state: techRplyHstryYn === "Y" ? "completed" : "pending",
|
||||
},
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "current" },
|
||||
];
|
||||
}
|
||||
|
||||
if (prgrsSttsComCd === "EP") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "completed" },
|
||||
{
|
||||
label: MAIN_STEP_LABELS.techReview,
|
||||
state: techRplyHstryYn === "Y" ? "completed" : "pending",
|
||||
},
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "completed" },
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
|
||||
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
function getProgressStatusBadge(prgrsSttsComCd?: string | null) {
|
||||
if (prgrsSttsComCd === "SL") {
|
||||
return <Badge color="orange">보완요청</Badge>;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getBadgeColor(state: BadgeState) {
|
||||
switch (state) {
|
||||
case "completed":
|
||||
return "green";
|
||||
case "current":
|
||||
return "blue";
|
||||
case "pending":
|
||||
return "lightGray";
|
||||
}
|
||||
}
|
||||
|
||||
function getBadgeStateText(state: BadgeState) {
|
||||
switch (state) {
|
||||
case "completed":
|
||||
return "완료";
|
||||
case "current":
|
||||
return "진행중";
|
||||
case "pending":
|
||||
return "대기";
|
||||
}
|
||||
}
|
||||
|
||||
/** 단계명을 2줄로 분리: "설치인가 신청" → ["설치인가", "신청"], "기술검토" → ["기술검토", ""] */
|
||||
function splitStepLabel(label: string) {
|
||||
const [title = "", ...rest] = label.split(" ");
|
||||
return { title, detail: rest.join(" ") };
|
||||
}
|
||||
|
||||
function formatOptional(value: string | undefined) {
|
||||
return value?.trim() ? value : "-";
|
||||
}
|
||||
|
||||
interface ProgressStepBadgeProps {
|
||||
prgrsSttsComCd: string;
|
||||
techRplyHstryYn: "Y" | "N";
|
||||
}
|
||||
|
||||
function ProgressStepBadge({ prgrsSttsComCd, techRplyHstryYn }: ProgressStepBadgeProps) {
|
||||
const badges = buildProgressBadges(prgrsSttsComCd, techRplyHstryYn);
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-center justify-center">
|
||||
{badges.map((badge, index) => (
|
||||
<Fragment key={`${badge.label}-${index}`}>
|
||||
{index > 0 && <span className="mx-2">></span>}
|
||||
{/* 1줄: 단계명 / 2줄: 세부단계 + 상태 (예: 설치인가 / 신청완료, 기술검토 / 완료) */}
|
||||
<Badge
|
||||
color={getBadgeColor(badge.state)}
|
||||
className="w-[100px] flex-col justify-center !px-0 py-1 text-center leading-tight"
|
||||
>
|
||||
<span>{splitStepLabel(badge.label).title}</span>
|
||||
<span>
|
||||
{splitStepLabel(badge.label).detail}
|
||||
{getBadgeStateText(badge.state)}
|
||||
</span>
|
||||
</Badge>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function createSewerBscPlanColumns() {
|
||||
return [
|
||||
columnHelper.accessor("sido", {
|
||||
header: "시도",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
}),
|
||||
columnHelper.accessor("gugun", {
|
||||
header: "시군구",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
}),
|
||||
columnHelper.accessor("sewerBscPlanNm", {
|
||||
header: "기본계획명",
|
||||
meta: { align: "left" },
|
||||
cell: ({ row, getValue }) => (
|
||||
<>
|
||||
{getProgressStatusBadge(row.original.prgrsSttsComCd)} {formatOptional(getValue())}
|
||||
</>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("sewerBscPlanSeCdNm", {
|
||||
header: "계획구분",
|
||||
meta: { align: "center" },
|
||||
cell: ({ row }) =>
|
||||
formatOptional(row.original.sewerBscPlanSeCdNm || row.original.sewerBscPlanSeComCd),
|
||||
}),
|
||||
columnHelper.accessor("taskSeCdNm", {
|
||||
header: "업무구분",
|
||||
meta: { align: "center" },
|
||||
cell: ({ row }) => formatOptional(row.original.taskSeCdNm || row.original.taskSeComCd),
|
||||
}),
|
||||
columnHelper.accessor("rgtrNm", {
|
||||
header: "담당자",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
}),
|
||||
columnHelper.accessor("envoSeCdNm", {
|
||||
header: "해당기관",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: "progress",
|
||||
header: "진행상황",
|
||||
meta: { align: "center" },
|
||||
cell: ({ row }) => (
|
||||
<ProgressStepBadge
|
||||
prgrsSttsComCd={row.original.prgrsSttsComCd || ""}
|
||||
techRplyHstryYn={row.original.techRplyHstryYn || "N"}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("regDt", {
|
||||
header: "등록일",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => {
|
||||
const value = getValue();
|
||||
return value ? dayjs(value).format("YYYY-MM-DD") : "-";
|
||||
},
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useFormContext, useWatch } from "react-hook-form";
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
import { NON_NEGATIVE_INPUT_PROPS, sanitizeNumberInput } from "@/shared/lib";
|
||||
import { SectionCollapsible } from "@/shared/ui";
|
||||
import { Field } from "@/shared/ui/field";
|
||||
import { truncateDecimals } from "@/widgets/pbadms-sprt";
|
||||
import { PreviousPlanLookupButton } from "@/widgets/previous-plan-lookup";
|
||||
import {
|
||||
Button,
|
||||
Input,
|
||||
Table,
|
||||
TableBody,
|
||||
@@ -21,14 +19,6 @@ import {
|
||||
|
||||
import type { SewerBscPlanRegistFormData } from "../../lib/sewer-bsc-plan-regist-schema";
|
||||
|
||||
const PreviousPlanLookupDialog = dynamic(
|
||||
() =>
|
||||
import("../previous-plan-lookup-dialog").then((mod) => ({
|
||||
default: mod.PreviousPlanLookupDialog,
|
||||
})),
|
||||
{ ssr: false },
|
||||
);
|
||||
|
||||
const OTLN_ROWS = [
|
||||
{ key: "sewerPlanPpltnCnt" as const, label: "계획인구", decimals: 0 },
|
||||
{ key: "sewerPrcsPpltnCnt" as const, label: "처리인구", decimals: 0 },
|
||||
@@ -37,7 +27,6 @@ const OTLN_ROWS = [
|
||||
|
||||
function OutlineFormSection() {
|
||||
const { control, setValue, getValues } = useFormContext<SewerBscPlanRegistFormData>();
|
||||
const [isLookupOpen, setIsLookupOpen] = useState(false);
|
||||
|
||||
const otlnList = useWatch({ control, name: "sewerBscPlanOtlnList" }) ?? [];
|
||||
const frlvlGoalYr = useWatch({ control, name: "sewerBscPlanDtl.frlvlGoalYr" });
|
||||
@@ -74,11 +63,7 @@ function OutlineFormSection() {
|
||||
|
||||
return (
|
||||
<SectionCollapsible title="사업개요">
|
||||
<div className="mb-2 flex justify-end px-2">
|
||||
<Button type="button" color="white" onClick={() => setIsLookupOpen(true)}>
|
||||
이전기본계획조회
|
||||
</Button>
|
||||
</div>
|
||||
<PreviousPlanLookupButton sidoNm={sido} gugunNm={gugun} />
|
||||
<Table caption="사업개요 목록으로 구분, 1단계, 2단계, 3단계, 4단계 정보 제공" variant="secondary">
|
||||
<colgroup>
|
||||
<col width="12%" />
|
||||
@@ -126,13 +111,6 @@ function OutlineFormSection() {
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
<PreviousPlanLookupDialog
|
||||
open={isLookupOpen}
|
||||
onOpenChange={setIsLookupOpen}
|
||||
initialSidoNm={sido}
|
||||
initialGugunNm={gugun}
|
||||
/>
|
||||
</SectionCollapsible>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,11 +6,14 @@ import type {
|
||||
SewerBscPlanOtlnResponseDto,
|
||||
} from "@/entities/sewer-bsc-plan";
|
||||
import { fmtNumber } from "@/widgets/pbadms-sprt";
|
||||
import { PreviousPlanLookupButton } from "@/widgets/previous-plan-lookup";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components";
|
||||
|
||||
interface OutlineSectionProps {
|
||||
otlnList: SewerBscPlanOtlnResponseDto[];
|
||||
dtl?: SewerBscPlanDtlResponseDto;
|
||||
sido?: string | null;
|
||||
gugun?: string | null;
|
||||
}
|
||||
|
||||
const OTLN_ROWS = [
|
||||
@@ -19,7 +22,7 @@ const OTLN_ROWS = [
|
||||
{ key: "sewerPsplyRt" as const, label: "하수도보급율" },
|
||||
];
|
||||
|
||||
function OutlineSection({ otlnList, dtl }: OutlineSectionProps) {
|
||||
function OutlineSection({ otlnList, dtl, sido, gugun }: OutlineSectionProps) {
|
||||
const stepHeaders = [
|
||||
{ planStpNo: 1, label: "1단계", year: dtl?.frlvlGoalYr },
|
||||
{ planStpNo: 2, label: "2단계", year: dtl?.sclvlGoalYr },
|
||||
@@ -29,6 +32,7 @@ function OutlineSection({ otlnList, dtl }: OutlineSectionProps) {
|
||||
|
||||
return (
|
||||
<SectionCollapsible title="사업개요">
|
||||
<PreviousPlanLookupButton sidoNm={sido} gugunNm={gugun} />
|
||||
<Table caption="사업개요 목록으로 구분, 1단계, 2단계, 3단계, 4단계 정보 제공" variant="secondary">
|
||||
<colgroup>
|
||||
<col width="12%" />
|
||||
|
||||
@@ -1,357 +1,354 @@
|
||||
"use client";
|
||||
|
||||
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
|
||||
|
||||
import { dayjs, getDefaultFileKey, useTus } from "@/shared/lib";
|
||||
import { TextShimmer } from "@/shared/ui";
|
||||
import { SectionCollapsible } from "@/shared/ui";
|
||||
import { Field, FieldLabel, FieldSet } from "@/shared/ui/field";
|
||||
import { AttachmentDownloadLink, attachService } from "@/entities/attach";
|
||||
import type { SewerBscPlanRvwOpnnResponseDto } from "@/entities/sewer-bsc-plan";
|
||||
import {
|
||||
Button,
|
||||
RadioGroup,
|
||||
RadioGroupItem,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
Textarea,
|
||||
} from "@common/components";
|
||||
|
||||
import type {
|
||||
ReviewOpinionFormData,
|
||||
ReviewOpinionGetFormDataResult,
|
||||
} from "../../lib/review-opinion-utils";
|
||||
import type { ReviewStepCode } from "../../model/view-types";
|
||||
import { REVIEW_STEP_LABELS } from "../../model/view-types";
|
||||
|
||||
const RVW_OPNN_FILE_KEY_PREFIX = "rvw-opnn::";
|
||||
|
||||
interface TechReviewOpinionSectionProps {
|
||||
rvwList: SewerBscPlanRvwOpnnResponseDto[];
|
||||
editableReviewStep?: ReviewStepCode;
|
||||
atrzSeComCdList?: string[];
|
||||
isAtrzLoading?: boolean;
|
||||
}
|
||||
|
||||
export interface TechReviewOpinionSectionHandle {
|
||||
getFormData: () => ReviewOpinionGetFormDataResult | null;
|
||||
resetForm: () => void;
|
||||
}
|
||||
|
||||
interface TechReviewOpinionTableProps {
|
||||
items: SewerBscPlanRvwOpnnResponseDto[];
|
||||
}
|
||||
|
||||
interface UploadedOpinionFile {
|
||||
fileName: string;
|
||||
fileKey: string;
|
||||
atflId?: string;
|
||||
atflCd?: string;
|
||||
}
|
||||
|
||||
const EMPTY_FORM_DATA: ReviewOpinionFormData = {
|
||||
rvwRsltCn: "",
|
||||
rvwRsltAnmlyYn: "",
|
||||
atchFileGroupId: "",
|
||||
opnrSeComCd: "",
|
||||
};
|
||||
|
||||
function createRvwOpnnUploadKey(file: File) {
|
||||
return `${RVW_OPNN_FILE_KEY_PREFIX}${getDefaultFileKey(file)}`;
|
||||
}
|
||||
|
||||
function hasFormContent(formData: ReviewOpinionFormData) {
|
||||
return Boolean(formData.rvwRsltCn.trim() || formData.rvwRsltAnmlyYn || formData.atchFileGroupId);
|
||||
}
|
||||
|
||||
const TechReviewOpinionSection = forwardRef<
|
||||
TechReviewOpinionSectionHandle,
|
||||
TechReviewOpinionSectionProps
|
||||
>(function TechReviewOpinionSection(
|
||||
{ rvwList, editableReviewStep, atrzSeComCdList = [], isAtrzLoading = false },
|
||||
ref,
|
||||
) {
|
||||
const canWriteKecoOpinion = !isAtrzLoading && atrzSeComCdList.includes("ATRZ02");
|
||||
const [formData, setFormData] = useState<ReviewOpinionFormData>({
|
||||
...EMPTY_FORM_DATA,
|
||||
opnrSeComCd: editableReviewStep ?? "",
|
||||
});
|
||||
const [uploadedFile, setUploadedFile] = useState<UploadedOpinionFile | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
opnrSeComCd: editableReviewStep ?? "",
|
||||
}));
|
||||
}, [editableReviewStep]);
|
||||
|
||||
const { uploadFileAsync, isUploading, uploads } = useTus({
|
||||
onSuccess: (atflCd, atflId, _file, fileKey) => {
|
||||
if (!atflCd || !atflId) return;
|
||||
|
||||
setFormData((prev) => ({ ...prev, atchFileGroupId: atflCd }));
|
||||
setUploadedFile((prev) => (prev?.fileKey === fileKey ? { ...prev, atflCd, atflId } : prev));
|
||||
},
|
||||
onError: (_error, _file, fileKey) => {
|
||||
setUploadedFile((prev) => (prev?.fileKey === fileKey ? null : prev));
|
||||
},
|
||||
});
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
getFormData: () => {
|
||||
if (!editableReviewStep || !hasFormContent(formData)) {
|
||||
return null;
|
||||
}
|
||||
if (editableReviewStep === "KECO" && !canWriteKecoOpinion) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
formData: {
|
||||
...formData,
|
||||
opnrSeComCd: editableReviewStep,
|
||||
},
|
||||
rvwList,
|
||||
};
|
||||
},
|
||||
resetForm: () => {
|
||||
setFormData({
|
||||
...EMPTY_FORM_DATA,
|
||||
opnrSeComCd: editableReviewStep ?? "",
|
||||
});
|
||||
setUploadedFile(null);
|
||||
},
|
||||
}),
|
||||
[canWriteKecoOpinion, editableReviewStep, formData, rvwList],
|
||||
);
|
||||
|
||||
const envRvwList = rvwList.filter((item) => item.opnrSeComCd === "ENV");
|
||||
const kecoRvwList = rvwList.filter((item) => item.opnrSeComCd === "KECO");
|
||||
|
||||
const hasEnvSection = envRvwList.length > 0 || editableReviewStep === "ENV";
|
||||
const hasKecoSection =
|
||||
kecoRvwList.length > 0 || (editableReviewStep === "KECO" && canWriteKecoOpinion);
|
||||
|
||||
if (!hasEnvSection && !hasKecoSection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
async function handleFileUpload(files: FileList | null) {
|
||||
if (!files || files.length === 0) return;
|
||||
|
||||
const file = files[0];
|
||||
const fileKey = createRvwOpnnUploadKey(file);
|
||||
const existingAtflCd = formData.atchFileGroupId;
|
||||
|
||||
setUploadedFile({ fileName: file.name, fileKey });
|
||||
|
||||
try {
|
||||
await uploadFileAsync(file, {
|
||||
fileKey,
|
||||
metadata: existingAtflCd ? { atflCd: existingAtflCd } : undefined,
|
||||
});
|
||||
} catch {
|
||||
// onError callback handles cleanup
|
||||
}
|
||||
}
|
||||
|
||||
async function handleFileRemove() {
|
||||
if (!uploadedFile) return;
|
||||
|
||||
uploads[uploadedFile.fileKey]?.abort();
|
||||
|
||||
if (uploadedFile.atflId) {
|
||||
try {
|
||||
await attachService.deleteAttachmentByAtflId(uploadedFile.atflId);
|
||||
} catch (error) {
|
||||
console.error("검토의견 첨부파일 삭제 실패:", error);
|
||||
}
|
||||
}
|
||||
|
||||
setUploadedFile(null);
|
||||
setFormData((prev) => ({ ...prev, atchFileGroupId: "" }));
|
||||
}
|
||||
|
||||
function renderOpinionForm(reviewStep: ReviewStepCode) {
|
||||
if (editableReviewStep !== reviewStep) return null;
|
||||
if (reviewStep === "KECO" && !canWriteKecoOpinion) return null;
|
||||
|
||||
const uploading = uploadedFile ? (isUploading[uploadedFile.fileKey] ?? false) : false;
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-xl font-semibold">검토의견 작성</h4>
|
||||
<Table caption="검토의견 작성으로 의견내용, 검토결과, 첨부파일 정보 제공" variant="secondary">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[12rem]">의견내용</TableHead>
|
||||
<TableCell>
|
||||
<Textarea
|
||||
value={formData.rvwRsltCn}
|
||||
onChange={(event) =>
|
||||
setFormData((prev) => ({ ...prev, rvwRsltCn: event.target.value }))
|
||||
}
|
||||
placeholder="의견내용을 입력하세요"
|
||||
rows={4}
|
||||
className="min-h-[8rem] !text-xl"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>검토결과</TableHead>
|
||||
<TableCell>
|
||||
<FieldSet>
|
||||
<RadioGroup
|
||||
value={formData.rvwRsltAnmlyYn}
|
||||
onValueChange={(value) =>
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
rvwRsltAnmlyYn: value as ReviewOpinionFormData["rvwRsltAnmlyYn"],
|
||||
}))
|
||||
}
|
||||
>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<FieldLabel
|
||||
htmlFor={`rvw-opnn-normal-${reviewStep}`}
|
||||
className="!w-auto border-none"
|
||||
>
|
||||
<Field orientation="horizontal" className="w-auto">
|
||||
<RadioGroupItem value="N" id={`rvw-opnn-normal-${reviewStep}`} />
|
||||
<span className="font-normal">정상</span>
|
||||
</Field>
|
||||
</FieldLabel>
|
||||
<FieldLabel
|
||||
htmlFor={`rvw-opnn-abnormal-${reviewStep}`}
|
||||
className="!w-auto border-none"
|
||||
>
|
||||
<Field orientation="horizontal" className="w-auto">
|
||||
<RadioGroupItem value="Y" id={`rvw-opnn-abnormal-${reviewStep}`} />
|
||||
<span className="font-normal">이상</span>
|
||||
</Field>
|
||||
</FieldLabel>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</FieldSet>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>첨부파일</TableHead>
|
||||
<TableCell>
|
||||
{uploadedFile ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<TextShimmer className="text-xl" active={uploading}>
|
||||
{uploadedFile.fileName}
|
||||
</TextShimmer>
|
||||
<Button
|
||||
size="icon"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
void handleFileRemove();
|
||||
}}
|
||||
disabled={uploading}
|
||||
className="size-8 !bg-destructive"
|
||||
>
|
||||
<i className="diveicon di-x text-xs" aria-label="삭제" />
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<label>
|
||||
<input
|
||||
type="file"
|
||||
className="hidden"
|
||||
onChange={(event) => {
|
||||
void handleFileUpload(event.target.files);
|
||||
event.target.value = "";
|
||||
}}
|
||||
/>
|
||||
<Button size="sm" variant="outline" type="button" asChild>
|
||||
<span>파일 선택</span>
|
||||
</Button>
|
||||
</label>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{hasEnvSection ? (
|
||||
<SectionCollapsible title={`${REVIEW_STEP_LABELS.ENV} 검토의견`}>
|
||||
<div className="space-y-6">
|
||||
{envRvwList.length > 0 ? <TechReviewOpinionTable items={envRvwList} /> : null}
|
||||
{renderOpinionForm("ENV")}
|
||||
</div>
|
||||
</SectionCollapsible>
|
||||
) : null}
|
||||
{hasKecoSection ? (
|
||||
<SectionCollapsible title={`${REVIEW_STEP_LABELS.KECO} 검토의견`}>
|
||||
<div className="space-y-6">
|
||||
{kecoRvwList.length > 0 ? <TechReviewOpinionTable items={kecoRvwList} /> : null}
|
||||
{renderOpinionForm("KECO")}
|
||||
</div>
|
||||
</SectionCollapsible>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
function TechReviewOpinionTable({ items }: TechReviewOpinionTableProps) {
|
||||
return (
|
||||
<div className="overflow-x-auto">
|
||||
<Table caption="검토의견 목록으로 의견자명, 의견내용, 검토결과, 첨부파일, 등록일시 정보 제공" variant="secondary">
|
||||
<colgroup>
|
||||
<col width="10%" />
|
||||
<col width="55%" />
|
||||
<col width="5%" />
|
||||
<col width="20%" />
|
||||
<col width="10%" />
|
||||
</colgroup>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>의견자명</TableHead>
|
||||
<TableHead>의견내용</TableHead>
|
||||
<TableHead>검토결과</TableHead>
|
||||
<TableHead>첨부파일</TableHead>
|
||||
<TableHead>등록일시</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{items.map((item) => (
|
||||
<TableRow key={`${item.sewerBscPlanDocNo}_${item.rvwOpnnSn}`}>
|
||||
<TableCell align="center">{item.opnrNm}</TableCell>
|
||||
<TableCell align="left">{item.rvwRsltCn || "-"}</TableCell>
|
||||
<TableCell align="center">
|
||||
{item.rvwRsltAnmlyYn === "Y" ? "이상" : item.rvwRsltAnmlyYn === "N" ? "정상" : "-"}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
{item.atchFileInfo?.atflCd ? (
|
||||
<AttachmentDownloadLink
|
||||
atflCd={item.atchFileInfo.atflCd}
|
||||
fileName={item.atchFileInfo.orgnlFileNm || "다운로드"}
|
||||
/>
|
||||
) : (
|
||||
"-"
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
{item.regDt ? dayjs(item.regDt).format("YYYY-MM-DD HH:mm:ss") : "-"}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { TechReviewOpinionSection };
|
||||
"use client";
|
||||
|
||||
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
|
||||
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { getDefaultFileKey, useTus, useUserStore } from "@/shared/lib";
|
||||
import { TextShimmer } from "@/shared/ui";
|
||||
import { SectionCollapsible } from "@/shared/ui";
|
||||
import { Field, FieldLabel, FieldSet } from "@/shared/ui/field";
|
||||
import { attachService } from "@/entities/attach";
|
||||
import {
|
||||
sewerBscPlanKeys,
|
||||
type SewerBscPlanRvwOpnnResponseDto,
|
||||
sewerBscPlanRvwOpnnService,
|
||||
} from "@/entities/sewer-bsc-plan";
|
||||
import { RvwOpnnListTable } from "@/widgets/pbadms-sprt";
|
||||
import {
|
||||
Button,
|
||||
RadioGroup,
|
||||
RadioGroupItem,
|
||||
Table,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
Textarea,
|
||||
} from "@common/components";
|
||||
|
||||
import type {
|
||||
ReviewOpinionFormData,
|
||||
ReviewOpinionGetFormDataResult,
|
||||
} from "../../lib/review-opinion-utils";
|
||||
import type { ReviewStepCode } from "../../model/view-types";
|
||||
import { REVIEW_STEP_LABELS } from "../../model/view-types";
|
||||
|
||||
const RVW_OPNN_FILE_KEY_PREFIX = "rvw-opnn::";
|
||||
|
||||
interface TechReviewOpinionSectionProps {
|
||||
rvwList: SewerBscPlanRvwOpnnResponseDto[];
|
||||
editableReviewStep?: ReviewStepCode;
|
||||
atrzSeComCdList?: string[];
|
||||
isAtrzLoading?: boolean;
|
||||
}
|
||||
|
||||
export interface TechReviewOpinionSectionHandle {
|
||||
getFormData: () => ReviewOpinionGetFormDataResult | null;
|
||||
resetForm: () => void;
|
||||
}
|
||||
|
||||
interface TechReviewOpinionTableProps {
|
||||
items: SewerBscPlanRvwOpnnResponseDto[];
|
||||
}
|
||||
|
||||
interface UploadedOpinionFile {
|
||||
fileName: string;
|
||||
fileKey: string;
|
||||
atflId?: string;
|
||||
atflCd?: string;
|
||||
}
|
||||
|
||||
const EMPTY_FORM_DATA: ReviewOpinionFormData = {
|
||||
rvwRsltCn: "",
|
||||
rvwRsltAnmlyYn: "",
|
||||
atchFileGroupId: "",
|
||||
opnrSeComCd: "",
|
||||
};
|
||||
|
||||
function createRvwOpnnUploadKey(file: File) {
|
||||
return `${RVW_OPNN_FILE_KEY_PREFIX}${getDefaultFileKey(file)}`;
|
||||
}
|
||||
|
||||
function hasFormContent(formData: ReviewOpinionFormData) {
|
||||
return Boolean(formData.rvwRsltCn.trim() || formData.rvwRsltAnmlyYn || formData.atchFileGroupId);
|
||||
}
|
||||
|
||||
const TechReviewOpinionSection = forwardRef<
|
||||
TechReviewOpinionSectionHandle,
|
||||
TechReviewOpinionSectionProps
|
||||
>(function TechReviewOpinionSection(
|
||||
{ rvwList, editableReviewStep, atrzSeComCdList = [], isAtrzLoading = false },
|
||||
ref,
|
||||
) {
|
||||
const canWriteKecoOpinion = !isAtrzLoading && atrzSeComCdList.includes("ATRZ02");
|
||||
const [formData, setFormData] = useState<ReviewOpinionFormData>({
|
||||
...EMPTY_FORM_DATA,
|
||||
opnrSeComCd: editableReviewStep ?? "",
|
||||
});
|
||||
const [uploadedFile, setUploadedFile] = useState<UploadedOpinionFile | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
opnrSeComCd: editableReviewStep ?? "",
|
||||
}));
|
||||
}, [editableReviewStep]);
|
||||
|
||||
const { uploadFileAsync, isUploading, uploads } = useTus({
|
||||
onSuccess: (atflCd, atflId, _file, fileKey) => {
|
||||
if (!atflCd || !atflId) return;
|
||||
|
||||
setFormData((prev) => ({ ...prev, atchFileGroupId: atflCd }));
|
||||
setUploadedFile((prev) => (prev?.fileKey === fileKey ? { ...prev, atflCd, atflId } : prev));
|
||||
},
|
||||
onError: (_error, _file, fileKey) => {
|
||||
setUploadedFile((prev) => (prev?.fileKey === fileKey ? null : prev));
|
||||
},
|
||||
});
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
getFormData: () => {
|
||||
if (!editableReviewStep || !hasFormContent(formData)) {
|
||||
return null;
|
||||
}
|
||||
if (editableReviewStep === "KECO" && !canWriteKecoOpinion) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
formData: {
|
||||
...formData,
|
||||
opnrSeComCd: editableReviewStep,
|
||||
},
|
||||
rvwList,
|
||||
};
|
||||
},
|
||||
resetForm: () => {
|
||||
setFormData({
|
||||
...EMPTY_FORM_DATA,
|
||||
opnrSeComCd: editableReviewStep ?? "",
|
||||
});
|
||||
setUploadedFile(null);
|
||||
},
|
||||
}),
|
||||
[canWriteKecoOpinion, editableReviewStep, formData, rvwList],
|
||||
);
|
||||
|
||||
const envRvwList = rvwList.filter((item) => item.opnrSeComCd === "ENV");
|
||||
const kecoRvwList = rvwList.filter((item) => item.opnrSeComCd === "KECO");
|
||||
|
||||
const hasEnvSection = envRvwList.length > 0 || editableReviewStep === "ENV";
|
||||
const hasKecoSection =
|
||||
kecoRvwList.length > 0 || (editableReviewStep === "KECO" && canWriteKecoOpinion);
|
||||
|
||||
if (!hasEnvSection && !hasKecoSection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
async function handleFileUpload(files: FileList | null) {
|
||||
if (!files || files.length === 0) return;
|
||||
|
||||
const file = files[0];
|
||||
const fileKey = createRvwOpnnUploadKey(file);
|
||||
const existingAtflCd = formData.atchFileGroupId;
|
||||
|
||||
setUploadedFile({ fileName: file.name, fileKey });
|
||||
|
||||
try {
|
||||
await uploadFileAsync(file, {
|
||||
fileKey,
|
||||
metadata: existingAtflCd ? { atflCd: existingAtflCd } : undefined,
|
||||
});
|
||||
} catch {
|
||||
// onError callback handles cleanup
|
||||
}
|
||||
}
|
||||
|
||||
async function handleFileRemove() {
|
||||
if (!uploadedFile) return;
|
||||
|
||||
uploads[uploadedFile.fileKey]?.abort();
|
||||
|
||||
if (uploadedFile.atflId) {
|
||||
try {
|
||||
await attachService.deleteAttachmentByAtflId(uploadedFile.atflId);
|
||||
} catch (error) {
|
||||
console.error("검토의견 첨부파일 삭제 실패:", error);
|
||||
}
|
||||
}
|
||||
|
||||
setUploadedFile(null);
|
||||
setFormData((prev) => ({ ...prev, atchFileGroupId: "" }));
|
||||
}
|
||||
|
||||
function isReviewTurn(reviewStep: ReviewStepCode) {
|
||||
if (editableReviewStep !== reviewStep) return false;
|
||||
if (reviewStep === "KECO" && !canWriteKecoOpinion) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function renderOpinionForm(reviewStep: ReviewStepCode) {
|
||||
if (!isReviewTurn(reviewStep)) return null;
|
||||
|
||||
const uploading = uploadedFile ? (isUploading[uploadedFile.fileKey] ?? false) : false;
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-xl font-semibold">검토의견 작성</h4>
|
||||
<Table
|
||||
caption="검토의견 작성으로 의견내용, 검토결과, 첨부파일 정보 제공"
|
||||
variant="secondary"
|
||||
>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[12rem]">의견내용</TableHead>
|
||||
<TableCell>
|
||||
<Textarea
|
||||
value={formData.rvwRsltCn}
|
||||
onChange={(event) =>
|
||||
setFormData((prev) => ({ ...prev, rvwRsltCn: event.target.value }))
|
||||
}
|
||||
placeholder="의견내용을 입력하세요"
|
||||
rows={4}
|
||||
className="min-h-[8rem] !text-xl"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>검토결과</TableHead>
|
||||
<TableCell>
|
||||
<FieldSet>
|
||||
<RadioGroup
|
||||
value={formData.rvwRsltAnmlyYn}
|
||||
onValueChange={(value) =>
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
rvwRsltAnmlyYn: value as ReviewOpinionFormData["rvwRsltAnmlyYn"],
|
||||
}))
|
||||
}
|
||||
>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<FieldLabel
|
||||
htmlFor={`rvw-opnn-normal-${reviewStep}`}
|
||||
className="!w-auto border-none"
|
||||
>
|
||||
<Field orientation="horizontal" className="w-auto">
|
||||
<RadioGroupItem value="N" id={`rvw-opnn-normal-${reviewStep}`} />
|
||||
<span className="font-normal">정상</span>
|
||||
</Field>
|
||||
</FieldLabel>
|
||||
<FieldLabel
|
||||
htmlFor={`rvw-opnn-abnormal-${reviewStep}`}
|
||||
className="!w-auto border-none"
|
||||
>
|
||||
<Field orientation="horizontal" className="w-auto">
|
||||
<RadioGroupItem value="Y" id={`rvw-opnn-abnormal-${reviewStep}`} />
|
||||
<span className="font-normal">이상</span>
|
||||
</Field>
|
||||
</FieldLabel>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</FieldSet>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>첨부파일</TableHead>
|
||||
<TableCell>
|
||||
{uploadedFile ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<TextShimmer className="text-xl" active={uploading}>
|
||||
{uploadedFile.fileName}
|
||||
</TextShimmer>
|
||||
<Button
|
||||
size="icon"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
void handleFileRemove();
|
||||
}}
|
||||
disabled={uploading}
|
||||
className="size-8 !bg-destructive"
|
||||
>
|
||||
<i className="diveicon di-x text-xs" aria-label="삭제" />
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<label>
|
||||
<input
|
||||
type="file"
|
||||
className="hidden"
|
||||
onChange={(event) => {
|
||||
void handleFileUpload(event.target.files);
|
||||
event.target.value = "";
|
||||
}}
|
||||
/>
|
||||
<Button size="sm" variant="outline" type="button" asChild>
|
||||
<span>파일 선택</span>
|
||||
</Button>
|
||||
</label>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{hasEnvSection ? (
|
||||
<SectionCollapsible title={`${REVIEW_STEP_LABELS.ENV} 검토의견`}>
|
||||
<div className="space-y-6">
|
||||
{envRvwList.length > 0 ? (
|
||||
<TechReviewOpinionTable items={envRvwList} />
|
||||
) : null}
|
||||
{renderOpinionForm("ENV")}
|
||||
</div>
|
||||
</SectionCollapsible>
|
||||
) : null}
|
||||
{hasKecoSection ? (
|
||||
<SectionCollapsible title={`${REVIEW_STEP_LABELS.KECO} 검토의견`}>
|
||||
<div className="space-y-6">
|
||||
{kecoRvwList.length > 0 ? (
|
||||
<TechReviewOpinionTable items={kecoRvwList} />
|
||||
) : null}
|
||||
{renderOpinionForm("KECO")}
|
||||
</div>
|
||||
</SectionCollapsible>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
function getRvwOpnnIds(item: SewerBscPlanRvwOpnnResponseDto) {
|
||||
if (!item.sewerBscPlanDocNo || item.chgCycl == null || item.rvwOpnnSn == null) {
|
||||
throw new Error("검토의견 식별값이 없습니다.");
|
||||
}
|
||||
return [item.sewerBscPlanDocNo, item.chgCycl, item.rvwOpnnSn] as const;
|
||||
}
|
||||
|
||||
function TechReviewOpinionTable({ items }: TechReviewOpinionTableProps) {
|
||||
const queryClient = useQueryClient();
|
||||
const currentUserUuid = useUserStore((state) => state.userInfo?.userUnqId);
|
||||
|
||||
return (
|
||||
<RvwOpnnListTable
|
||||
items={items}
|
||||
getRowKey={(item) => `${item.sewerBscPlanDocNo}_${item.rvwOpnnSn}`}
|
||||
currentUserUuid={currentUserUuid}
|
||||
onUpdate={async (item, body) => {
|
||||
await sewerBscPlanRvwOpnnService.update(...getRvwOpnnIds(item), body);
|
||||
await queryClient.invalidateQueries({ queryKey: sewerBscPlanKeys.all });
|
||||
}}
|
||||
onDelete={async (item) => {
|
||||
await sewerBscPlanRvwOpnnService.delete(...getRvwOpnnIds(item));
|
||||
await queryClient.invalidateQueries({ queryKey: sewerBscPlanKeys.all });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export { TechReviewOpinionSection };
|
||||
|
||||
@@ -81,8 +81,8 @@ function SewerBscPlanListPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4 px-[1.8rem] pb-6">
|
||||
<section className="card card-border mx-0 bg-background">
|
||||
<div className="card card-border !mt-6">
|
||||
<div className="card card-border">
|
||||
<form className="flex flex-wrap items-center gap-4 p-2" onSubmit={handleSearch}>
|
||||
<div className="flex items-center gap-2">
|
||||
<Label htmlFor="sidoComCd" className="whitespace-nowrap">
|
||||
@@ -165,7 +165,7 @@ function SewerBscPlanListPage() {
|
||||
</div>
|
||||
<Button type="submit">조회</Button>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<SewerBscPlanList
|
||||
searchParams={searchParams}
|
||||
|
||||
@@ -85,15 +85,15 @@ function SewerBscPlanList({ searchParams, pagination, onPaginationChange }: Sewe
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className="card card-border mx-0 bg-background pb-8 pt-0">
|
||||
<div className="card card-border pt-0">
|
||||
<div className="card-header flex items-center justify-between">
|
||||
<h3 className="text-2xl font-semibold">하수도기본계획 목록</h3>
|
||||
<h3 className="text-2xl font-medium text-label">하수도기본계획 목록</h3>
|
||||
<Button type="button" color="green" onClick={() => setIsApplyDialogOpen(true)}>
|
||||
신청
|
||||
</Button>
|
||||
</div>
|
||||
{tableComponent}
|
||||
</section>
|
||||
<div className="card !m-[1.2rem] !p-0">{tableComponent}</div>
|
||||
</div>
|
||||
<SewerBscPlanApplyDialog
|
||||
open={isApplyDialogOpen}
|
||||
onOpenChange={setIsApplyDialogOpen}
|
||||
|
||||
@@ -114,7 +114,12 @@ function SewerBscPlanViewPage({ id, propChgCycl }: SewerBscPlanViewPageProps) {
|
||||
<BasicInfoSection det={detDto} dtl={activeDtlDto} />
|
||||
<ContactInfoSection dtl={activeDtlDto} />
|
||||
<ElaprDocPrgrsStatus elaprDocNo={detDto.sewerBscPlanDocNo} />
|
||||
<OutlineSection otlnList={otlnList} dtl={activeDtlDto} />
|
||||
<OutlineSection
|
||||
otlnList={otlnList}
|
||||
dtl={activeDtlDto}
|
||||
sido={detDto.sido}
|
||||
gugun={detDto.gugun}
|
||||
/>
|
||||
<BizCstMatrixSection bizCstList={bizCstList} dtl={activeDtlDto} />
|
||||
<FnrsCstSection fnrsCstList={fnrsCstList} dtl={activeDtlDto} />
|
||||
<TmplntSection tmplntList={tmplntList} det={detDto} dtl={activeDtlDto} />
|
||||
|
||||
@@ -12,11 +12,13 @@ export const ENDPOINT = {
|
||||
RVW_MNPW: "/as-service/api/v1/rvw-mnpw",
|
||||
BNS_IF: "/as-service/api/v1/if",
|
||||
INSTL_SNCN: "/as-service/api/v1/instl-sncn",
|
||||
INSTL_SNCN_RVW_OPNN: "/as-service/api/v1/instl-sncn-rvw-opnn",
|
||||
CKLST: "/as-service/api/v1/cklst",
|
||||
CKLST_ARTCL: "/as-service/api/v1/cklst-artcl",
|
||||
CKLST_RVW: "/as-service/api/v1/cklst-rvw",
|
||||
ADSTDSTR: "/as-service/api/v1/adstdstr",
|
||||
FNRS_CNSLTN_MNG: "/as-service/api/v1/fnrs-cnsltn-mng",
|
||||
FNRS_CNSLTN_RVW_OPNN: "/as-service/api/v1/fnrs-cnsltn-rvw-opnn",
|
||||
FNRS_SHAR_SE: "/as-service/api/v1/fnrs-shar-se-mng",
|
||||
DESIGN_CNSTN: "/as-service/api/v1/design-cnstn",
|
||||
DESIGN_CNSTN_BIZ_CST: "/as-service/api/v1/design-cnstn-biz-cst",
|
||||
@@ -24,6 +26,7 @@ export const ENDPOINT = {
|
||||
DESIGN_CNSTN_ESNTL_ATCH_FILE: "/as-service/api/v1/design-cnstn-esntl-atch-file",
|
||||
DESIGN_CNSTN_RVW_OPNN: "/as-service/api/v1/design-cnstn-rvw-opnn",
|
||||
SEWER_BSC_PLAN: "/as-service/api/v1/sewer-bsc-plans",
|
||||
SEWER_BSC_PLAN_RVW_OPNNS: "/as-service/api/v1/sewer-bsc-plan-rvw-opnns",
|
||||
DASHBOARD: "/as-service/api/v1/dashboard",
|
||||
DCRAG_TO: "/as-service/api/v1/dcrag-to",
|
||||
AI_AUTO_PRCS_MNG: "/as-service/api/v1/ai-auto-prcs-mng",
|
||||
|
||||
@@ -13,4 +13,4 @@ export const AS_DASHBOARD_BBS_ID = process.env.NEXT_PUBLIC_AS_DASHBOARD_BBS_ID;
|
||||
|
||||
/** 웹한글 기안기(WebHWP) 컨트롤 URL */
|
||||
export const WEBHYP_CTRL_URL =
|
||||
process.env.NEXT_PUBLIC_WEBHYP_CTRL_URL || "http://hwp.onasys.co.kr/webhwpctrl";
|
||||
process.env.NEXT_PUBLIC_WEBHYP_CTRL_URL || "https://hwp.onasys.co.kr/webhwpctrl";
|
||||
|
||||
@@ -46,6 +46,7 @@ export type {
|
||||
PbadmsMngDetResponseBase,
|
||||
RvwOpnnResponseExtension,
|
||||
RvwOpnnSaveBase,
|
||||
RvwOpnnUpdateRequestDto,
|
||||
SplmntDmndResponseExtension,
|
||||
SplmntDmndSaveBase,
|
||||
} from "./pbadms-sprt";
|
||||
|
||||
@@ -231,11 +231,22 @@ export interface PbadmsApplySaveHeader {
|
||||
rvwStpCd?: string;
|
||||
}
|
||||
|
||||
/** 검토의견 수정 공통 요청 DTO */
|
||||
export interface RvwOpnnUpdateRequestDto {
|
||||
opnrSeComCd?: string;
|
||||
opnrNm?: string;
|
||||
rvwRsltCn?: string;
|
||||
rvwRsltAnmlyYn?: string;
|
||||
atchFileGroupId?: string;
|
||||
}
|
||||
|
||||
/** 검토의견 응답 공통 확장 필드 */
|
||||
export interface RvwOpnnResponseExtension {
|
||||
opnrSeCdNm?: string;
|
||||
atchFileInfo?: AtflMngClientResponseDto;
|
||||
regDt?: string;
|
||||
rgtr?: string;
|
||||
rgtrNm?: string;
|
||||
}
|
||||
|
||||
/** 보완요구 응답 공통 확장 필드 */
|
||||
|
||||
@@ -51,6 +51,7 @@ export {
|
||||
} from "./ui/pbadms-task-apply-dialog";
|
||||
export { PrivacyConsentSection } from "./ui/privacy-consent-section";
|
||||
export { ProgressStatusSection } from "./ui/progress-status-section";
|
||||
export { RvwOpnnListTable, type RvwOpnnListTableProps } from "./ui/rvw-opnn-list-table";
|
||||
export {
|
||||
SplmntDmndDialog,
|
||||
type SplmntDmndDialogConfirmItem,
|
||||
|
||||
@@ -404,11 +404,17 @@ function AttachmentsFormSection({
|
||||
<TableHead
|
||||
scope="row"
|
||||
className="group break-keep font-medium"
|
||||
data-required={file.esntlAtchYn === "Y"}
|
||||
align="left"
|
||||
>
|
||||
<div className="inline-flex w-[calc(100%-1rem)] items-center justify-between gap-10">
|
||||
{file.esntlAtchFileNm}
|
||||
<span>
|
||||
{file.esntlAtchFileNm}
|
||||
{file.esntlAtchYn === "Y" && (
|
||||
<span className="whitespace-nowrap text-red-600" title="필수 항목">
|
||||
{" "}(*)
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
{atflCd ? (
|
||||
<Button
|
||||
size="sm"
|
||||
|
||||
@@ -0,0 +1,403 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
|
||||
import { dayjs, getDefaultFileKey, useTus } from "@/shared/lib";
|
||||
import type { AtflMngClientResponseDto, RvwOpnnUpdateRequestDto } from "@/shared/model";
|
||||
import { AttachmentFileField } from "@/shared/ui";
|
||||
import { Field, FieldLabel, FieldSet } from "@/shared/ui/field";
|
||||
import {
|
||||
AttachmentDownloadLink,
|
||||
attachService,
|
||||
useDownloadAttachmentByAtflId,
|
||||
} from "@/entities/attach";
|
||||
import {
|
||||
Button,
|
||||
RadioGroup,
|
||||
RadioGroupItem,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
Textarea,
|
||||
} from "@common/components";
|
||||
|
||||
const RVW_OPNN_EDIT_FILE_KEY_PREFIX = "rvw-opnn-edit::";
|
||||
|
||||
/** 업무별 검토의견 응답 DTO 공통 필드 (업무마다 null 허용 여부가 달라 느슨하게 정의) */
|
||||
interface RvwOpnnListItem {
|
||||
opnrSeComCd?: string | null;
|
||||
opnrNm?: string | null;
|
||||
rvwRsltCn?: string | null;
|
||||
rvwRsltAnmlyYn?: string | null;
|
||||
atchFileGroupId?: string | null;
|
||||
atchFileInfo?: AtflMngClientResponseDto | null;
|
||||
regDt?: string | null;
|
||||
rgtr?: string | null;
|
||||
}
|
||||
|
||||
interface RvwOpnnListTableProps<T extends RvwOpnnListItem> {
|
||||
items: T[];
|
||||
getRowKey: (item: T) => string;
|
||||
/** 로그인 사용자 UUID — 검토의견 등록자(rgtr)와 같을 때만 수정·삭제 버튼 노출 */
|
||||
currentUserUuid?: string;
|
||||
onUpdate: (item: T, body: RvwOpnnUpdateRequestDto) => Promise<void>;
|
||||
onDelete: (item: T) => Promise<void>;
|
||||
}
|
||||
|
||||
interface EditDraft {
|
||||
rvwRsltCn: string;
|
||||
rvwRsltAnmlyYn: string;
|
||||
atchFileGroupId: string;
|
||||
}
|
||||
|
||||
interface EditFile {
|
||||
fileName: string;
|
||||
fileKey: string;
|
||||
atflId?: string;
|
||||
atflCd?: string;
|
||||
/** 이번 수정 중 새로 업로드한 파일 여부 (취소 시 정리 대상) */
|
||||
isNew: boolean;
|
||||
}
|
||||
|
||||
function formatAnmlyYn(value?: string | null) {
|
||||
if (value === "Y") return "이상";
|
||||
if (value === "N") return "정상";
|
||||
return "-";
|
||||
}
|
||||
|
||||
function RvwOpnnListTable<T extends RvwOpnnListItem>({
|
||||
items,
|
||||
getRowKey,
|
||||
currentUserUuid,
|
||||
onUpdate,
|
||||
onDelete,
|
||||
}: RvwOpnnListTableProps<T>) {
|
||||
const { confirm } = useConfirm();
|
||||
const { mutate: downloadByAtflId } = useDownloadAttachmentByAtflId();
|
||||
const [editingKey, setEditingKey] = useState<string | null>(null);
|
||||
const [draft, setDraft] = useState<EditDraft | null>(null);
|
||||
const [editFile, setEditFile] = useState<EditFile | null>(null);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const { uploadFileAsync, isUploading, uploads } = useTus({
|
||||
onSuccess: (atflCd, atflId, _file, fileKey) => {
|
||||
if (!atflCd || !atflId) return;
|
||||
|
||||
setDraft((prev) => (prev ? { ...prev, atchFileGroupId: atflCd } : prev));
|
||||
setEditFile((prev) => (prev?.fileKey === fileKey ? { ...prev, atflCd, atflId } : prev));
|
||||
},
|
||||
onError: (_error, _file, fileKey) => {
|
||||
setEditFile((prev) => (prev?.fileKey === fileKey ? null : prev));
|
||||
},
|
||||
});
|
||||
|
||||
const editFileUploading = editFile ? (isUploading[editFile.fileKey] ?? false) : false;
|
||||
|
||||
function canModify(item: T) {
|
||||
return Boolean(currentUserUuid) && item.rgtr === currentUserUuid;
|
||||
}
|
||||
|
||||
async function discardNewFile(file: EditFile | null) {
|
||||
if (!file?.isNew) return;
|
||||
|
||||
uploads[file.fileKey]?.abort();
|
||||
if (file.atflId) {
|
||||
try {
|
||||
await attachService.deleteAttachmentByAtflId(file.atflId);
|
||||
} catch (error) {
|
||||
console.error("검토의견 첨부파일 삭제 실패:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resetEdit() {
|
||||
setEditingKey(null);
|
||||
setDraft(null);
|
||||
setEditFile(null);
|
||||
}
|
||||
|
||||
function startEdit(item: T) {
|
||||
void discardNewFile(editFile);
|
||||
|
||||
setEditingKey(getRowKey(item));
|
||||
setDraft({
|
||||
rvwRsltCn: item.rvwRsltCn ?? "",
|
||||
rvwRsltAnmlyYn: item.rvwRsltAnmlyYn ?? "",
|
||||
atchFileGroupId: item.atchFileGroupId ?? "",
|
||||
});
|
||||
setEditFile(
|
||||
item.atchFileInfo?.atflCd
|
||||
? {
|
||||
fileName: item.atchFileInfo.orgnlFileNm || "첨부파일",
|
||||
fileKey: `${RVW_OPNN_EDIT_FILE_KEY_PREFIX}${item.atchFileInfo.atflCd}`,
|
||||
atflId: item.atchFileInfo.atflId,
|
||||
atflCd: item.atchFileInfo.atflCd,
|
||||
isNew: false,
|
||||
}
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
function cancelEdit() {
|
||||
void discardNewFile(editFile);
|
||||
resetEdit();
|
||||
}
|
||||
|
||||
async function handleFileAdd(files: FileList | null) {
|
||||
if (!files || files.length === 0) return;
|
||||
|
||||
const file = files[0];
|
||||
const fileKey = `${RVW_OPNN_EDIT_FILE_KEY_PREFIX}${getDefaultFileKey(file)}`;
|
||||
|
||||
setEditFile({ fileName: file.name, fileKey, isNew: true });
|
||||
|
||||
try {
|
||||
await uploadFileAsync(file, { fileKey });
|
||||
} catch {
|
||||
// onError callback handles cleanup
|
||||
}
|
||||
}
|
||||
|
||||
function handleFileRemove() {
|
||||
// 기존 파일은 저장 시점에 연결만 해제하고, 새로 올린 파일은 즉시 정리
|
||||
void discardNewFile(editFile);
|
||||
setEditFile(null);
|
||||
setDraft((prev) => (prev ? { ...prev, atchFileGroupId: "" } : prev));
|
||||
}
|
||||
|
||||
async function handleSave(item: T) {
|
||||
if (!draft) return;
|
||||
if (!draft.rvwRsltCn.trim()) {
|
||||
alert("의견내용을 입력하세요.");
|
||||
return;
|
||||
}
|
||||
if (!draft.rvwRsltAnmlyYn) {
|
||||
alert("검토결과를 선택하세요.");
|
||||
return;
|
||||
}
|
||||
if (!(await confirm("검토의견을 수정하시겠습니까?"))) return;
|
||||
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
await onUpdate(item, {
|
||||
opnrSeComCd: item.opnrSeComCd ?? undefined,
|
||||
opnrNm: item.opnrNm ?? undefined,
|
||||
rvwRsltCn: draft.rvwRsltCn,
|
||||
rvwRsltAnmlyYn: draft.rvwRsltAnmlyYn,
|
||||
atchFileGroupId: draft.atchFileGroupId,
|
||||
});
|
||||
alert("수정되었습니다.");
|
||||
resetEdit();
|
||||
} catch (error) {
|
||||
console.error("검토의견 수정 실패:", error);
|
||||
alert("검토의견 수정 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDelete(item: T) {
|
||||
if (!(await confirm("검토의견을 삭제하시겠습니까?"))) return;
|
||||
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
await onDelete(item);
|
||||
alert("삭제되었습니다.");
|
||||
if (editingKey === getRowKey(item)) resetEdit();
|
||||
} catch (error) {
|
||||
console.error("검토의견 삭제 실패:", error);
|
||||
alert("검토의견 삭제 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
}
|
||||
|
||||
function renderViewRow(item: T) {
|
||||
return (
|
||||
<>
|
||||
<TableCell align="left">{item.rvwRsltCn || "-"}</TableCell>
|
||||
<TableCell align="center">{formatAnmlyYn(item.rvwRsltAnmlyYn)}</TableCell>
|
||||
<TableCell align="center">
|
||||
{item.atchFileInfo?.atflCd ? (
|
||||
<AttachmentDownloadLink
|
||||
atflCd={item.atchFileInfo.atflCd}
|
||||
fileName={item.atchFileInfo.orgnlFileNm || "다운로드"}
|
||||
/>
|
||||
) : (
|
||||
"-"
|
||||
)}
|
||||
</TableCell>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function renderEditRow(rowKey: string, editDraft: EditDraft) {
|
||||
return (
|
||||
<>
|
||||
<TableCell align="left">
|
||||
<Textarea
|
||||
value={editDraft.rvwRsltCn}
|
||||
onChange={(event) =>
|
||||
setDraft((prev) => (prev ? { ...prev, rvwRsltCn: event.target.value } : prev))
|
||||
}
|
||||
placeholder="의견내용을 입력하세요"
|
||||
rows={4}
|
||||
className="min-h-[8rem] !text-xl"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
<FieldSet>
|
||||
<RadioGroup
|
||||
value={editDraft.rvwRsltAnmlyYn}
|
||||
onValueChange={(value) =>
|
||||
setDraft((prev) => (prev ? { ...prev, rvwRsltAnmlyYn: value } : prev))
|
||||
}
|
||||
>
|
||||
<div className="flex flex-col gap-2">
|
||||
<FieldLabel
|
||||
htmlFor={`rvw-opnn-edit-normal-${rowKey}`}
|
||||
className="!w-auto border-none"
|
||||
>
|
||||
<Field orientation="horizontal" className="w-auto">
|
||||
<RadioGroupItem value="N" id={`rvw-opnn-edit-normal-${rowKey}`} />
|
||||
<span className="font-normal">정상</span>
|
||||
</Field>
|
||||
</FieldLabel>
|
||||
<FieldLabel
|
||||
htmlFor={`rvw-opnn-edit-abnormal-${rowKey}`}
|
||||
className="!w-auto border-none"
|
||||
>
|
||||
<Field orientation="horizontal" className="w-auto">
|
||||
<RadioGroupItem value="Y" id={`rvw-opnn-edit-abnormal-${rowKey}`} />
|
||||
<span className="font-normal">이상</span>
|
||||
</Field>
|
||||
</FieldLabel>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</FieldSet>
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
<AttachmentFileField
|
||||
files={
|
||||
editFile
|
||||
? [
|
||||
{
|
||||
fileKey: editFile.fileKey,
|
||||
fileName: editFile.fileName,
|
||||
onDownload: editFile.atflId
|
||||
? () => downloadByAtflId(editFile.atflId!)
|
||||
: undefined,
|
||||
},
|
||||
]
|
||||
: []
|
||||
}
|
||||
inputId={`rvw-opnn-edit-${rowKey}-file-input`}
|
||||
isUploadingByKey={isUploading}
|
||||
multiple={false}
|
||||
showAddButton={!editFile}
|
||||
addDisabled={editFileUploading}
|
||||
onAdd={(fileList) => void handleFileAdd(fileList)}
|
||||
onRemove={handleFileRemove}
|
||||
/>
|
||||
</TableCell>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="overflow-x-auto">
|
||||
<Table
|
||||
caption="검토의견 목록으로 의견자명, 의견내용, 검토결과, 첨부파일, 등록일시, 관리 정보 제공"
|
||||
variant="secondary"
|
||||
>
|
||||
<colgroup>
|
||||
<col width="10%" />
|
||||
<col width="47%" />
|
||||
<col width="6%" />
|
||||
<col width="17%" />
|
||||
<col width="10%" />
|
||||
<col width="10%" />
|
||||
</colgroup>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>의견자명</TableHead>
|
||||
<TableHead>의견내용</TableHead>
|
||||
<TableHead>검토결과</TableHead>
|
||||
<TableHead>첨부파일</TableHead>
|
||||
<TableHead>등록일시</TableHead>
|
||||
<TableHead>관리</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{items.map((item) => {
|
||||
const rowKey = getRowKey(item);
|
||||
const isEditing = editingKey === rowKey && draft !== null;
|
||||
|
||||
return (
|
||||
<TableRow key={rowKey}>
|
||||
<TableCell align="center">{item.opnrNm}</TableCell>
|
||||
{isEditing ? renderEditRow(rowKey, draft) : renderViewRow(item)}
|
||||
<TableCell align="center">
|
||||
{item.regDt ? dayjs(item.regDt).format("YYYY-MM-DD HH:mm:ss") : "-"}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
{!canModify(item) ? (
|
||||
"-"
|
||||
) : isEditing ? (
|
||||
<div className="flex flex-wrap justify-center gap-1">
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
onClick={() => void handleSave(item)}
|
||||
disabled={isSubmitting || editFileUploading}
|
||||
>
|
||||
저장
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
color="white"
|
||||
onClick={cancelEdit}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
취소
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-wrap justify-center gap-1">
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
color="white"
|
||||
onClick={() => startEdit(item)}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
수정
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
color="gray"
|
||||
onClick={() => void handleDelete(item)}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
삭제
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { RvwOpnnListTable, type RvwOpnnListTableProps };
|
||||
@@ -0,0 +1,4 @@
|
||||
export {
|
||||
PreviousPlanLookupButton,
|
||||
type PreviousPlanLookupButtonProps,
|
||||
} from "./ui/previous-plan-lookup-button";
|
||||
@@ -0,0 +1,46 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
import { Button } from "@common/components";
|
||||
|
||||
const PreviousPlanLookupDialog = dynamic(
|
||||
() =>
|
||||
import("./previous-plan-lookup-dialog").then((mod) => ({
|
||||
default: mod.PreviousPlanLookupDialog,
|
||||
})),
|
||||
{ ssr: false },
|
||||
);
|
||||
|
||||
interface PreviousPlanLookupButtonProps {
|
||||
/** 조회 다이얼로그 시도 초기값(표시명) */
|
||||
sidoNm?: string | null;
|
||||
/** 조회 다이얼로그 시군구 초기값(표시명) */
|
||||
gugunNm?: string | null;
|
||||
}
|
||||
|
||||
/** 섹션 상단 우측에 [이전기본계획조회] 버튼과 조회 다이얼로그를 배치한다. */
|
||||
function PreviousPlanLookupButton({ sidoNm, gugunNm }: PreviousPlanLookupButtonProps) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-2 flex justify-end px-2">
|
||||
<Button type="button" color="white" onClick={() => setIsOpen(true)}>
|
||||
이전기본계획조회
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<PreviousPlanLookupDialog
|
||||
open={isOpen}
|
||||
onOpenChange={setIsOpen}
|
||||
initialSidoNm={sidoNm}
|
||||
initialGugunNm={gugunNm}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export { PreviousPlanLookupButton };
|
||||
export type { PreviousPlanLookupButtonProps };
|
||||
+33
-14
@@ -51,7 +51,7 @@ function PreviousPlanLookupDialog({
|
||||
const [gnsvyYr, setGnsvyYr] = useState("");
|
||||
const [swtrmtFcltCd, setSwtrmtFcltCd] = useState("");
|
||||
const [hasPrefillApplied, setHasPrefillApplied] = useState(false);
|
||||
const [searchedClsfSystmNm, setSearchedClsfSystmNm] = useState("");
|
||||
const [hasGugunPrefillApplied, setHasGugunPrefillApplied] = useState(false);
|
||||
|
||||
const { data: sidoData, isPending: isSidoPending } = useFindSidoCombo();
|
||||
const { data: gugunData, isPending: isGugunPending } = useFindGugunCombo(sidoComCd);
|
||||
@@ -62,15 +62,19 @@ function PreviousPlanLookupDialog({
|
||||
const ctpvNm = sidoOptions.find((option) => option.adstDstrCd === sidoComCd)?.adstDstrNm ?? "";
|
||||
const sggNm = gugunOptions.find((option) => option.adstDstrCd === sggComCd)?.adstDstrNm ?? "";
|
||||
|
||||
// 창 오픈 시 시도/시군구로 조회하고, 콤보 선택에 따라 파라미터가 추가되며 재조회된다.
|
||||
// 빈 값은 서비스에서 제외되며, 시도가 없으면 조회하지 않는다.
|
||||
const searchParams = {
|
||||
swtrmtFcltCd,
|
||||
ctpvNm,
|
||||
sggNm,
|
||||
targetYear: gnsvyYr,
|
||||
swtrmtFcltCd,
|
||||
};
|
||||
const isSearchEnabled = open && hasGugunPrefillApplied && Boolean(ctpvNm);
|
||||
|
||||
const { data, isFetching, refetch } = useNowDmgMtncGnsvy(searchParams, false);
|
||||
const planData = gnsvyYr && swtrmtFcltCd ? (data?.[0] ?? null) : null;
|
||||
const { data, isFetching, refetch } = useNowDmgMtncGnsvy(searchParams, isSearchEnabled);
|
||||
const planData = isSearchEnabled ? (data?.[0] ?? null) : null;
|
||||
const clsfSystmNm = planData ? sggNm || ctpvNm : "";
|
||||
|
||||
const { data: yearTree, isPending: isYearPending } = useLvNowDmgMtncGnsvy({
|
||||
level: "2",
|
||||
@@ -97,8 +101,8 @@ function PreviousPlanLookupDialog({
|
||||
setSggComCd("");
|
||||
setGnsvyYr("");
|
||||
setSwtrmtFcltCd("");
|
||||
setSearchedClsfSystmNm("");
|
||||
setHasPrefillApplied(false);
|
||||
setHasGugunPrefillApplied(false);
|
||||
return;
|
||||
}
|
||||
}, [open]);
|
||||
@@ -115,12 +119,27 @@ function PreviousPlanLookupDialog({
|
||||
setHasPrefillApplied(true);
|
||||
}, [open, hasPrefillApplied, isSidoPending, sidoOptions, initialSidoNm]);
|
||||
|
||||
// 시군구 초기값 적용이 끝난 뒤에 최초 조회가 나가도록 완료 여부를 표시한다.
|
||||
useEffect(() => {
|
||||
if (!open || !sidoComCd || isGugunPending || !initialGugunNm) return;
|
||||
if (sggComCd) return;
|
||||
if (!open || !hasPrefillApplied || hasGugunPrefillApplied) return;
|
||||
if (!sidoComCd || !initialGugunNm) {
|
||||
setHasGugunPrefillApplied(true);
|
||||
return;
|
||||
}
|
||||
if (isGugunPending) return;
|
||||
|
||||
const matchedGugun = gugunOptions.find((option) => option.adstDstrNm === initialGugunNm);
|
||||
if (matchedGugun) setSggComCd(matchedGugun.adstDstrCd);
|
||||
}, [open, sidoComCd, isGugunPending, gugunOptions, initialGugunNm, sggComCd]);
|
||||
setHasGugunPrefillApplied(true);
|
||||
}, [
|
||||
open,
|
||||
hasPrefillApplied,
|
||||
hasGugunPrefillApplied,
|
||||
sidoComCd,
|
||||
isGugunPending,
|
||||
gugunOptions,
|
||||
initialGugunNm,
|
||||
]);
|
||||
|
||||
function handleSidoChange(value: string) {
|
||||
setSidoComCd(value);
|
||||
@@ -145,18 +164,18 @@ function PreviousPlanLookupDialog({
|
||||
}
|
||||
|
||||
async function handleSearch() {
|
||||
if (!ctpvNm) {
|
||||
alert("시도를 선택하세요.");
|
||||
return;
|
||||
}
|
||||
const { data: list, error } = await refetch();
|
||||
if (error) {
|
||||
alert("데이터 조회 중 오류가 발생했습니다.");
|
||||
setSearchedClsfSystmNm("");
|
||||
return;
|
||||
}
|
||||
if (!list?.length) {
|
||||
alert("조회된 데이터가 없습니다.");
|
||||
setSearchedClsfSystmNm("");
|
||||
return;
|
||||
}
|
||||
setSearchedClsfSystmNm(sggNm || ctpvNm);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -198,8 +217,8 @@ function PreviousPlanLookupDialog({
|
||||
|
||||
<SectionCollapsible title="기본계획파일" defaultOpen>
|
||||
<PreviousPlanFileSection
|
||||
key={searchedClsfSystmNm || "empty"}
|
||||
clsfSystmNm={searchedClsfSystmNm || undefined}
|
||||
key={clsfSystmNm || "empty"}
|
||||
clsfSystmNm={clsfSystmNm || undefined}
|
||||
/>
|
||||
</SectionCollapsible>
|
||||
|
||||
@@ -1,151 +1,13 @@
|
||||
.card {
|
||||
position: relative;
|
||||
margin: 0.6rem;
|
||||
box-sizing: border-box;
|
||||
min-height: 2rem;
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.6rem;
|
||||
font-size: 1.3rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.card_width10 {
|
||||
width: calc(10% - 12px);
|
||||
}
|
||||
|
||||
.card_width20 {
|
||||
width: calc(20% - 12px);
|
||||
}
|
||||
|
||||
.card_width25 {
|
||||
width: calc(25% - 12px);
|
||||
}
|
||||
|
||||
.card_width30 {
|
||||
width: calc(30% - 12px);
|
||||
}
|
||||
|
||||
.card_width33 {
|
||||
width: calc(33.333% - 12px);
|
||||
}
|
||||
|
||||
.card_width40 {
|
||||
width: calc(40% - 12px);
|
||||
}
|
||||
|
||||
.card_width45 {
|
||||
width: calc(45% - 12px);
|
||||
}
|
||||
|
||||
.card_width50 {
|
||||
width: calc(50% - 12px);
|
||||
}
|
||||
|
||||
.card_width60 {
|
||||
width: calc(60% - 12px);
|
||||
}
|
||||
|
||||
.card_width66 {
|
||||
width: calc(66.666% - 12px);
|
||||
}
|
||||
|
||||
.card_width70 {
|
||||
width: calc(70% - 12px);
|
||||
}
|
||||
|
||||
.card_width75 {
|
||||
width: calc(75% - 12px);
|
||||
}
|
||||
|
||||
.card_width80 {
|
||||
width: calc(80% - 12px);
|
||||
}
|
||||
|
||||
.card_width90 {
|
||||
width: calc(90% - 12px);
|
||||
}
|
||||
|
||||
.card_width100 {
|
||||
width: calc(100% - 12px);
|
||||
}
|
||||
|
||||
.card_nopadding {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.card_paddingnone {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.card_nomargin {
|
||||
width: 100%;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.card_title {
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.card_title .leftArea {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.card_title h4 {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.topline {
|
||||
border-top: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.bottomline {
|
||||
border-bottom: 1px solid #e6eef3;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.card-border {
|
||||
border: 1px solid #dbe2e6;
|
||||
background-color: rgba(255, 255, 255, 0.6);
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
/*
|
||||
* 카드 관련 클래스(.card, .card-border, .card-header, .card-footer, .card_width*, .card_nopadding,
|
||||
* .card_paddingnone, .card_nomargin, .card_title, .topline, .bottomline)와 테마 색상은
|
||||
* 공통 @common/components/index.css(layout/common.css) + @common/assets base.css 변수에서 관리.
|
||||
* 이 파일에는 앱 전용 레이아웃 차이와 페이지 전용 스타일만 둔다.
|
||||
*/
|
||||
|
||||
/* 앱 레이아웃 차이: 카드 헤더를 카드 상단 끝까지 붙임 (공통 .card-header 에 추가) */
|
||||
.card-header {
|
||||
margin-top: -0.6rem;
|
||||
margin-left: -0.6rem;
|
||||
margin-right: -0.6rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #e1e7eb;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
margin-left: -0.6rem;
|
||||
margin-right: -0.6rem;
|
||||
margin-top: 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-top: 1px solid #e1e7eb;
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* 블랙 테마 */
|
||||
[data-theme="black"] {
|
||||
.card-border {
|
||||
background-color: rgba(255, 255, 255, 0.065);
|
||||
border-color: #38383a;
|
||||
}
|
||||
.card-header {
|
||||
border-bottom-color: #3f4042;
|
||||
}
|
||||
}
|
||||
|
||||
/* 자료현황 스타일 */
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { isAxiosError } from "axios";
|
||||
import Image from "next/image";
|
||||
import { authService, tokenManager } from "@/entities/auth";
|
||||
import { client } from "@/shared/api";
|
||||
@@ -107,10 +108,12 @@ function DashboardPage() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching user info:", error);
|
||||
if (error instanceof Error && error.message.includes("401")) {
|
||||
// 401은 로그인 페이지로 이동하지 않고 오류 처리만 한다
|
||||
if (
|
||||
(isAxiosError(error) && error.response?.status === 401) ||
|
||||
(error instanceof Error && error.message.includes("401"))
|
||||
) {
|
||||
setError("인증이 만료되었습니다. 다시 로그인해주세요.");
|
||||
tokenManager.clearAuthToken();
|
||||
router.push("/auth/login");
|
||||
} else {
|
||||
setError("네트워크 오류가 발생했습니다.");
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ export const ENDPOINT = {
|
||||
REUSE_MAP: "/operation-service/api/v1/dashBoard/main/reuse/map",
|
||||
},
|
||||
IC_SERVICE: {
|
||||
GROUPING_STATISTICS: "/ic-service/api/v1/support-status-summary",
|
||||
GROUPING_STATISTICS: "/ic-service/api/v1/support-status-summary/public",
|
||||
},
|
||||
DC_SERVICE: {
|
||||
DATA_STATUS: "/dc-service/api/v1/dataStatus",
|
||||
|
||||
@@ -1,129 +1,11 @@
|
||||
.card {
|
||||
position: relative;
|
||||
margin: 0.6rem;
|
||||
box-sizing: border-box;
|
||||
min-height: 2rem;
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.6rem;
|
||||
font-size: 1.3rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.card_width10 {
|
||||
width: calc(10% - 12px);
|
||||
}
|
||||
|
||||
.card_width20 {
|
||||
width: calc(20% - 12px);
|
||||
}
|
||||
|
||||
.card_width25 {
|
||||
width: calc(25% - 12px);
|
||||
}
|
||||
|
||||
.card_width30 {
|
||||
width: calc(30% - 12px);
|
||||
}
|
||||
|
||||
.card_width33 {
|
||||
width: calc(33.333% - 12px);
|
||||
}
|
||||
|
||||
.card_width40 {
|
||||
width: calc(40% - 12px);
|
||||
}
|
||||
|
||||
.card_width45 {
|
||||
width: calc(45% - 12px);
|
||||
}
|
||||
|
||||
.card_width50 {
|
||||
width: calc(50% - 12px);
|
||||
}
|
||||
|
||||
.card_width60 {
|
||||
width: calc(60% - 12px);
|
||||
}
|
||||
|
||||
.card_width66 {
|
||||
width: calc(66.666% - 12px);
|
||||
}
|
||||
|
||||
.card_width70 {
|
||||
width: calc(70% - 12px);
|
||||
}
|
||||
|
||||
.card_width75 {
|
||||
width: calc(75% - 12px);
|
||||
}
|
||||
|
||||
.card_width80 {
|
||||
width: calc(80% - 12px);
|
||||
}
|
||||
|
||||
.card_width90 {
|
||||
width: calc(90% - 12px);
|
||||
}
|
||||
|
||||
.card_width100 {
|
||||
width: calc(100% - 12px);
|
||||
}
|
||||
|
||||
.card_nopadding {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.card_nomargin {
|
||||
width: 100%;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.topline {
|
||||
border-top: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.bottomline {
|
||||
border-bottom: 1px solid #e6eef3;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.card-border {
|
||||
border: 1px solid #dbe2e6;
|
||||
background-color: rgba(255, 255, 255, 0.6);
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
/*
|
||||
* 카드 관련 클래스(.card, .card-border, .card-header, .card-footer, .card_width*, .card_nopadding,
|
||||
* .card_paddingnone, .card_nomargin, .card_title, .topline, .bottomline)와 테마 색상은
|
||||
* 공통 @common/components/index.css(layout/common.css) + @common/assets base.css 변수에서 관리.
|
||||
* 이 파일에는 앱 전용 레이아웃 차이와 페이지 전용 스타일만 둔다.
|
||||
*/
|
||||
|
||||
/* 앱 레이아웃 차이: 카드 헤더를 카드 상단 끝까지 붙임 (공통 .card-header 에 추가) */
|
||||
.card-header {
|
||||
margin-top: -0.6rem;
|
||||
margin-left: -0.6rem;
|
||||
margin-right: -0.6rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #e1e7eb;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
margin-left: -0.6rem;
|
||||
margin-right: -0.6rem;
|
||||
margin-top: 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-top: 1px solid #e1e7eb;
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* 블랙 테마 */
|
||||
[data-theme="black"] {
|
||||
.card-border {
|
||||
background-color: rgba(255, 255, 255, 0.065);
|
||||
border-color: #38383a;
|
||||
}
|
||||
.card-header {
|
||||
border-bottom-color: #3f4042;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1992,42 +1992,6 @@ useEffect(() => {
|
||||
footerVerificationResult,
|
||||
]);
|
||||
|
||||
/** footerVerificationResult만 바뀐 경우에도 총계 스타일 즉시 재적용/초기화 */
|
||||
useEffect(() => {
|
||||
const gridView = gridRef.current?.gridView as
|
||||
| {
|
||||
columnByName?: (name: string) => {
|
||||
footer?: { styleName?: string } | Array<{ styleName?: string }>;
|
||||
} | null;
|
||||
refresh?: () => void;
|
||||
}
|
||||
| undefined;
|
||||
if (!gridView?.columnByName) return;
|
||||
|
||||
columns.forEach((col) => {
|
||||
if (!col.footer) return;
|
||||
try {
|
||||
const column = gridView.columnByName?.(col.name);
|
||||
if (!column?.footer) return;
|
||||
const styleName =
|
||||
resolveFooterVerificationStyleName(
|
||||
col,
|
||||
footerVerificationResult,
|
||||
) ?? "";
|
||||
if (Array.isArray(column.footer)) {
|
||||
column.footer.forEach((item) => {
|
||||
item.styleName = styleName;
|
||||
});
|
||||
} else {
|
||||
column.footer.styleName = styleName;
|
||||
}
|
||||
} catch {
|
||||
console.error('총계 스타일 재적용/초기화 error');
|
||||
}
|
||||
});
|
||||
gridView.refresh?.();
|
||||
}, [footerVerificationResult, columns]);
|
||||
|
||||
// 행·셀 클릭 이벤트 (검증 오류 툴팁은 hover만 — onShowTooltip / 빈 셀 오버레이)
|
||||
useEffect(() => {
|
||||
const gridView = gridRef.current?.gridView;
|
||||
@@ -2403,17 +2367,17 @@ useEffect(() => {
|
||||
<div className="data-grid-status-bubble flex flex-wrap items-center gap-x-4 gap-y-1">
|
||||
<span>
|
||||
진행상황:{" "}
|
||||
{statusInfo.laeSbmsnSttsCd === null ||
|
||||
statusInfo.laeSbmsnSttsCd === "0" ? (
|
||||
{statusInfo.lclgvSbmsnSttsCd === null ||
|
||||
statusInfo.lclgvSbmsnSttsCd === "0" ? (
|
||||
"미작성"
|
||||
) : statusInfo.laeSbmsnSttsCd === "1" ? (
|
||||
) : statusInfo.lclgvSbmsnSttsCd === "1" ? (
|
||||
<span className="animate-pulse">작성중</span>
|
||||
) : statusInfo.laeSbmsnSttsCd === "2" ? (
|
||||
) : statusInfo.lclgvSbmsnSttsCd === "2" ? (
|
||||
<span className="!text-[#2094fa]">제출</span>
|
||||
) : statusInfo.laeSbmsnSttsCd === "3" ? (
|
||||
<span className="!text-[#179d5b]">승인</span>
|
||||
) : statusInfo.laeSbmsnSttsCd === "4" ? (
|
||||
<span className="relative inline-flex pr-3 !text-[#e53e35]">
|
||||
) : statusInfo.lclgvSbmsnSttsCd === "3" ? (
|
||||
<span className="!text-[#1fd078]">승인</span>
|
||||
) : statusInfo.lclgvSbmsnSttsCd === "4" ? (
|
||||
<span className="relative inline-flex pr-3 !text-[#ff6c64]">
|
||||
반려
|
||||
<span className="absolute -right-0.5 -top-0.5 inline-flex size-[0.6125rem]">
|
||||
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-[#e53e35] opacity-75" />
|
||||
@@ -2432,21 +2396,21 @@ useEffect(() => {
|
||||
</span>
|
||||
<span>
|
||||
제출일:{" "}
|
||||
{statusInfo.laeSbmsnYmd
|
||||
? statusInfo.laeSbmsnYmd.length === 8 &&
|
||||
/^\d{8}$/.test(statusInfo.laeSbmsnYmd)
|
||||
? `${statusInfo.laeSbmsnYmd.slice(0, 4)}-${statusInfo.laeSbmsnYmd.slice(4, 6)}-${statusInfo.laeSbmsnYmd.slice(6, 8)}`
|
||||
: statusInfo.laeSbmsnYmd
|
||||
{statusInfo.lclgvSbmsnYmd
|
||||
? statusInfo.lclgvSbmsnYmd.length === 8 &&
|
||||
/^\d{8}$/.test(statusInfo.lclgvSbmsnYmd)
|
||||
? `${statusInfo.lclgvSbmsnYmd.slice(0, 4)}-${statusInfo.lclgvSbmsnYmd.slice(4, 6)}-${statusInfo.lclgvSbmsnYmd.slice(6, 8)}`
|
||||
: statusInfo.lclgvSbmsnYmd
|
||||
: "미제출"}
|
||||
</span>
|
||||
<span>
|
||||
담당자:{" "}
|
||||
{statusInfo.laePicNm ? statusInfo.laePicNm : "-"}
|
||||
{statusInfo.lclgvPicNm ? statusInfo.lclgvPicNm : "-"}
|
||||
</span>
|
||||
<span>
|
||||
연락처:{" "}
|
||||
{statusInfo.laePicTelNo
|
||||
? statusInfo.laePicTelNo
|
||||
{statusInfo.lclgvPicTelnoCn
|
||||
? statusInfo.lclgvPicTelnoCn
|
||||
: "-"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -63,14 +63,14 @@ const exceptionHistoryTabs: { value: ExceptionHistoryTabKey; label: string }[] =
|
||||
|
||||
const TITLE = "예외처리 내역 조회";
|
||||
/** 예외처리 건만 */
|
||||
const PRCS_YN_LIST = "2";
|
||||
const VRFC_STTS_CD_LIST = "2";
|
||||
|
||||
export type ExceptionRejectSubmitItem = {
|
||||
vrfcSn: number;
|
||||
statsYr: string;
|
||||
sewerBzmnCd: string;
|
||||
statsCd: string;
|
||||
mdfrId: string;
|
||||
mdfrUuid: string;
|
||||
rjctOpnnCn?: string;
|
||||
};
|
||||
|
||||
@@ -84,8 +84,8 @@ function prcsYnCodeToNumber(raw: string | number | undefined): number | undefine
|
||||
}
|
||||
|
||||
function exceptionPrcsYnNmCellStyle({ fieldName, row }: CellStyleCallbackParams): string | undefined {
|
||||
if (fieldName !== "prcsYnNm") return undefined;
|
||||
const code = prcsYnCodeToNumber(row.prcsYn);
|
||||
if (fieldName !== "vrfcSttsNm") return undefined;
|
||||
const code = prcsYnCodeToNumber(row.vrfcSttsCd);
|
||||
if (code === 0 || code === 4) return "rg-cell-vrf-prcs-bad";
|
||||
return "rg-cell-vrf-prcs-ok";
|
||||
}
|
||||
@@ -96,10 +96,10 @@ const exceptionHistoryColumnInfo: ColumnInfo = {
|
||||
{ name: "admdstNm", fieldName: "admdstNm", header: { text: "행정구역" }, editable: false },
|
||||
{ name: "vrfcTrgtNm", fieldName: "vrfcTrgtNm", header: { text: "검증대상" }, editable: false },
|
||||
{ name: "rsnExpln", fieldName: "rsnExpln", header: { text: "사유" }, editable: false },
|
||||
{ name: "nowVlCn", fieldName: "nowVlCn", header: { text: "입력값" }, editable: false },
|
||||
{ name: "compVlCn", fieldName: "compVlCn", header: { text: "기준값" }, editable: false },
|
||||
{ name: "prcsYnNm", fieldName: "prcsYnNm", header: { text: "처리여부" }, editable: false },
|
||||
{ name: "stVrfRjtComNm", fieldName: "stVrfRjtComNm", header: { text: "반려상태" }, editable: false },
|
||||
{ name: "nowCn", fieldName: "nowCn", header: { text: "입력값" }, editable: false },
|
||||
{ name: "cmprCn", fieldName: "cmprCn", header: { text: "기준값" }, editable: false },
|
||||
{ name: "vrfcSttsNm", fieldName: "vrfcSttsNm", header: { text: "처리여부" }, editable: false },
|
||||
{ name: "statsVrfcRjtComNm", fieldName: "statsVrfcRjtComNm", header: { text: "반려상태" }, editable: false },
|
||||
{ name: "prcsOpnnExpln", fieldName: "prcsOpnnExpln", header: { text: "처리의견" }, editable: false },
|
||||
{ name: "rjctOpnnCn", fieldName: "rjctOpnnCn", header: { text: "반려의견" }, editable: false },
|
||||
{ name: "regDt", fieldName: "regDt", header: { text: "등록일시" }, editable: false },
|
||||
@@ -112,7 +112,7 @@ const exceptionHistoryColumnInfo: ColumnInfo = {
|
||||
width: 104,
|
||||
styleName: "rg-cell-reject-mini-btn",
|
||||
},
|
||||
{ name: "prcsYn", fieldName: "prcsYn", header: { text: "검증결과상태코드" }, editable: false, visible: false },
|
||||
{ name: "vrfcSttsCd", fieldName: "vrfcSttsCd", header: { text: "검증결과상태코드" }, editable: false, visible: false },
|
||||
{ name: "vrfcSn", fieldName: "vrfcSn", header: { text: "vrfcSn" }, editable: false, visible: false },
|
||||
],
|
||||
columnLayout: [
|
||||
@@ -120,16 +120,16 @@ const exceptionHistoryColumnInfo: ColumnInfo = {
|
||||
"admdstNm",
|
||||
"vrfcTrgtNm",
|
||||
"rsnExpln",
|
||||
"nowVlCn",
|
||||
"compVlCn",
|
||||
"prcsYnNm",
|
||||
"stVrfRjtComNm",
|
||||
"nowCn",
|
||||
"cmprCn",
|
||||
"vrfcSttsNm",
|
||||
"statsVrfcRjtComNm",
|
||||
"prcsOpnnExpln",
|
||||
"rjctOpnnCn",
|
||||
"regDt",
|
||||
"mdfcnDt",
|
||||
"individualReject",
|
||||
"prcsYn",
|
||||
"vrfcSttsCd",
|
||||
"vrfcSn",
|
||||
],
|
||||
};
|
||||
@@ -196,8 +196,7 @@ export function ExceptionProcessingHistoryDialog({
|
||||
statsYr: queryContext.statsYr,
|
||||
statsCd: queryContext.statsCd,
|
||||
vrfcMthdCd,
|
||||
prcsYnList: PRCS_YN_LIST,
|
||||
rejectCodeList: "0,1,2",
|
||||
vrfcSttsCdList: VRFC_STTS_CD_LIST,
|
||||
page: pageIndex + 1,
|
||||
size: 30,
|
||||
};
|
||||
@@ -316,13 +315,13 @@ export function ExceptionProcessingHistoryDialog({
|
||||
admdstNm: item.admdstNm ?? item.sggNm ?? "-",
|
||||
vrfcTrgtNm: item.vrfcTrgtNm,
|
||||
rsnExpln: item.rsnExpln,
|
||||
nowVlCn: item.nowVlCn ?? "-",
|
||||
compVlCn: item.compVlCn ?? "-",
|
||||
prcsYnNm: item.prcsYnNm ?? "-",
|
||||
stVrfRjtComNm: item.stVrfRjtComNm ?? "-",
|
||||
nowCn: item.nowCn ?? "-",
|
||||
cmprCn: item.cmprCn ?? "-",
|
||||
vrfcSttsNm: item.vrfcSttsNm ?? "-",
|
||||
statsVrfcRjtComNm: item.statsVrfcRjtComNm ?? "-",
|
||||
prcsOpnnExpln: item.prcsOpnnExpln ?? "-",
|
||||
rjctOpnnCn: item.rjctOpnnCn ?? "-",
|
||||
prcsYn: item.prcsYn ?? "",
|
||||
vrfcSttsCd: item.vrfcSttsCd ?? "",
|
||||
regDt: (item.regDt ?? "").slice(0, 16),
|
||||
mdfcnDt: (item.mdfcnDt ?? "").slice(0, 16),
|
||||
individualReject: "개별 반려",
|
||||
@@ -401,7 +400,7 @@ export function ExceptionProcessingHistoryDialog({
|
||||
alert("사용자 정보를 불러오지 못했습니다.");
|
||||
return;
|
||||
}
|
||||
const mdfrId = userInfo.userUnqId;
|
||||
const mdfrUuid = userInfo.userUnqId;
|
||||
const trimmedReason = rejectReasonText.trim();
|
||||
|
||||
const items: ReviewInputSaveRequest[] = pendingRejectVrfcSns.map((vrfcSn) => ({
|
||||
@@ -409,7 +408,7 @@ export function ExceptionProcessingHistoryDialog({
|
||||
statsYr: queryContext.statsYr,
|
||||
sewerBzmnCd: queryContext.sewerBzmnCd,
|
||||
statsCd: queryContext.statsCd,
|
||||
mdfrId,
|
||||
mdfrUuid,
|
||||
vrfcSn: String(vrfcSn),
|
||||
...(trimmedReason ? { rjctOpnnCn: trimmedReason } : {}),
|
||||
}));
|
||||
|
||||
@@ -145,6 +145,9 @@ const SearchForm = (props: SearchFormProps = {}) => {
|
||||
const currentYear = new Date().getFullYear();
|
||||
const defaultYear = currentYear - 1;
|
||||
const isInput = Boolean(props.isInput);
|
||||
const isDetailInputId = props.tabKey
|
||||
? `${props.tabKey}-isDetail`
|
||||
: "isDetail";
|
||||
const sidoListKind = isInput ? "input" : "default";
|
||||
|
||||
const { data: sidoList } = useSWR<ApiResponse<SearchCondition>>(
|
||||
@@ -479,12 +482,12 @@ const SearchForm = (props: SearchFormProps = {}) => {
|
||||
<div className="flex items-center gap-2">
|
||||
<Label
|
||||
className="cursor-pointer text-label"
|
||||
htmlFor="isDetail"
|
||||
htmlFor={isDetailInputId}
|
||||
>
|
||||
세부 데이터 포함
|
||||
</Label>
|
||||
<Checkbox
|
||||
id="isDetail"
|
||||
id={isDetailInputId}
|
||||
checked={values.isDetail}
|
||||
onCheckedChange={(value) =>
|
||||
setValues({
|
||||
|
||||
@@ -399,7 +399,7 @@ const SidoSggFacilityNameSearchForm = ({
|
||||
swtrmtFcltNm: String(selectedFacilityRow.fcltNm ?? ""),
|
||||
prcsFcltCpct: Number(selectedFacilityRow.fcltCpct ?? 0),
|
||||
sewerBzmnCd: initialSearchValues?.bsnmCd?.trim() ?? "",
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
saveList,
|
||||
deleteList,
|
||||
};
|
||||
|
||||
@@ -101,9 +101,9 @@ const verificationSubmitResultColumnInfo: ColumnInfo = {
|
||||
{ name: "fcltNm", fieldName: "fcltNm", header: { text: "시설명" }, editable: false },
|
||||
{ name: "vrfcTrgtNm", fieldName: "vrfcTrgtNm", header: { text: "검증대상" }, editable: false },
|
||||
{ name: "rsnExpln", fieldName: "rsnExpln", header: { text: "사유" }, editable: false },
|
||||
{ name: "nowVlCn", fieldName: "nowVlCn", header: { text: "입력값" }, editable: false },
|
||||
{ name: "compVlCn", fieldName: "compVlCn", header: { text: "기준값" }, editable: false },
|
||||
{ name: "prcsYn", fieldName: "prcsYn", header: { text: "처리여부" }, editable: false },
|
||||
{ name: "nowCn", fieldName: "nowCn", header: { text: "입력값" }, editable: false },
|
||||
{ name: "cmprCn", fieldName: "cmprCn", header: { text: "기준값" }, editable: false },
|
||||
{ name: "vrfcSttsNm", fieldName: "vrfcSttsNm", header: { text: "처리여부" }, editable: false },
|
||||
{ name: "regDt", fieldName: "regDt", header: { text: "등록일시" }, editable: false },
|
||||
{ name: "mdfcnDt", fieldName: "mdfcnDt", header: { text: "갱신일시" }, editable: false },
|
||||
{
|
||||
@@ -120,9 +120,9 @@ const verificationSubmitResultColumnInfo: ColumnInfo = {
|
||||
"fcltNm",
|
||||
"vrfcTrgtNm",
|
||||
"rsnExpln",
|
||||
"nowVlCn",
|
||||
"compVlCn",
|
||||
"prcsYn",
|
||||
"nowCn",
|
||||
"cmprCn",
|
||||
"vrfcSttsNm",
|
||||
"regDt",
|
||||
"mdfcnDt",
|
||||
"prcsOpnnExpln",
|
||||
@@ -211,9 +211,9 @@ export function VerificationResultDialog({
|
||||
fcltNm: item.fcltNm ?? "-",
|
||||
vrfcTrgtNm: item.vrfcTrgtNm,
|
||||
rsnExpln: item.rsnExpln,
|
||||
nowVlCn: item.nowVlCn ?? "-",
|
||||
compVlCn: item.compVlCn ?? "-",
|
||||
prcsYn: item.prcsYnNm ?? PRCS_YN_LABEL[item.prcsYn] ?? item.prcsYn,
|
||||
nowCn: item.nowCn ?? "-",
|
||||
cmprCn: item.cmprCn ?? "-",
|
||||
vrfcSttsNm: item.vrfcSttsNm ?? PRCS_YN_LABEL[item.vrfcSttsCd] ?? item.vrfcSttsCd,
|
||||
regDt: (item.regDt ?? "").slice(0, 16),
|
||||
mdfcnDt: (item.mdfcnDt ?? "").slice(0, 16),
|
||||
prcsOpnnExpln: item.prcsOpnnExpln ?? "",
|
||||
@@ -232,8 +232,7 @@ export function VerificationResultDialog({
|
||||
statsYr: stableQueryContext.statsYr,
|
||||
statsCd: stableQueryContext.statsCd,
|
||||
vrfcMthdCd,
|
||||
prcsYnList: "0",
|
||||
rejectCodeList: "0,1,2",
|
||||
vrfcSttsCdList: "0",
|
||||
page: page + 1,
|
||||
size: 10000,
|
||||
};
|
||||
@@ -351,7 +350,7 @@ export function VerificationResultDialog({
|
||||
sewerBzmnCd: stableQueryContext.sewerBzmnCd,
|
||||
statsYr: stableQueryContext.statsYr,
|
||||
statsCd: stableQueryContext.statsCd,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
exceptionList: payload,
|
||||
});
|
||||
if (response.status === "success") {
|
||||
|
||||
@@ -113,7 +113,7 @@ function syncCheckedMapFromGrid(
|
||||
if (!entry) continue;
|
||||
const sn = Number(entry.vrfcSn);
|
||||
if (!Number.isFinite(sn)) continue;
|
||||
next.set(sn, String(entry.prcsYn ?? ""));
|
||||
next.set(sn, String(entry.vrfcSttsCd ?? ""));
|
||||
}
|
||||
return next;
|
||||
}
|
||||
@@ -124,15 +124,15 @@ function selectionHasNonExceptionResetAllowed(
|
||||
): boolean {
|
||||
for (const [vrfcSn, storedPrcsYn] of map) {
|
||||
const onPage = currentEntries.find((e) => Number(e.vrfcSn) === vrfcSn);
|
||||
const prcsYn = onPage ? onPage.prcsYn : storedPrcsYn;
|
||||
const prcsYn = onPage ? onPage.vrfcSttsCd : storedPrcsYn;
|
||||
if (!canExceptionReset(prcsYn)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function verificationPrcsYnNmCellStyle({ fieldName, row }: CellStyleCallbackParams): string | undefined {
|
||||
if (fieldName !== "prcsYnNm") return undefined;
|
||||
const code = prcsYnCodeToNumber(row.prcsYn);
|
||||
if (fieldName !== "vrfcSttsNm") return undefined;
|
||||
const code = prcsYnCodeToNumber(row.vrfcSttsCd);
|
||||
if (code === 0 || code === 3) return "rg-cell-vrf-prcs-bad";
|
||||
return "rg-cell-vrf-prcs-ok";
|
||||
}
|
||||
@@ -144,15 +144,15 @@ const verificationResultColumnInfo: ColumnInfo = {
|
||||
{ name: "fcltNm", fieldName: "fcltNm", header: { text: "시설명" }, editable: false },
|
||||
{ name: "vrfcTrgtNm", fieldName: "vrfcTrgtNm", header: { text: "검증대상" }, editable: false },
|
||||
{ name: "rsnExpln", fieldName: "rsnExpln", header: { text: "사유" }, editable: false },
|
||||
{ name: "nowVlCn", fieldName: "nowVlCn", header: { text: "입력값" }, editable: false },
|
||||
{ name: "compVlCn", fieldName: "compVlCn", header: { text: "기준값" }, editable: false },
|
||||
{ name: "prcsYnNm", fieldName: "prcsYnNm", header: { text: "처리여부" }, editable: false },
|
||||
{ name: "stVrfRjtComNm", fieldName: "stVrfRjtComNm", header: { text: "반려상태" }, editable: false },
|
||||
{ name: "nowCn", fieldName: "nowCn", header: { text: "입력값" }, editable: false },
|
||||
{ name: "cmprCn", fieldName: "cmprCn", header: { text: "기준값" }, editable: false },
|
||||
{ name: "vrfcSttsNm", fieldName: "vrfcSttsNm", header: { text: "처리여부" }, editable: false },
|
||||
{ name: "statsVrfcRjtComNm", fieldName: "statsVrfcRjtComNm", header: { text: "반려상태" }, editable: false },
|
||||
{ name: "prcsOpnnExpln", fieldName: "prcsOpnnExpln", header: { text: "처리의견" }, editable: false },
|
||||
{ name: "rjctOpnnCn", fieldName: "rjctOpnnCn", header: { text: "반려의견" }, editable: false },
|
||||
{ name: "regDt", fieldName: "regDt", header: { text: "등록일시" }, editable: false },
|
||||
{ name: "mdfcnDt", fieldName: "mdfcnDt", header: { text: "갱신일시" }, editable: false },
|
||||
{ name: "prcsYn", fieldName: "prcsYn", header: { text: "검증결과상태코드" }, editable: false, visible: false },
|
||||
{ name: "vrfcSttsCd", fieldName: "vrfcSttsCd", header: { text: "검증결과상태코드" }, editable: false, visible: false },
|
||||
{ name: "vrfcSn", fieldName: "vrfcSn", header: { text: "검증일련번호" }, editable: false, visible: false },
|
||||
],
|
||||
columnLayout: [
|
||||
@@ -161,15 +161,15 @@ const verificationResultColumnInfo: ColumnInfo = {
|
||||
"fcltNm",
|
||||
"vrfcTrgtNm",
|
||||
"rsnExpln",
|
||||
"nowVlCn",
|
||||
"compVlCn",
|
||||
"prcsYnNm",
|
||||
"stVrfRjtComNm",
|
||||
"nowCn",
|
||||
"cmprCn",
|
||||
"vrfcSttsNm",
|
||||
"statsVrfcRjtComNm",
|
||||
"prcsOpnnExpln",
|
||||
"rjctOpnnCn",
|
||||
"regDt",
|
||||
"mdfcnDt",
|
||||
"prcsYn",
|
||||
"vrfcSttsCd",
|
||||
"vrfcSn",
|
||||
],
|
||||
};
|
||||
@@ -262,8 +262,7 @@ export function VerificationResultHistoryDialog({
|
||||
statsYr: stableQueryContext.statsYr,
|
||||
statsCd: stableQueryContext.statsCd,
|
||||
vrfcMthdCd,
|
||||
prcsYnList: "0,2",
|
||||
rejectCodeList: "0,1,2",
|
||||
vrfcSttsCdList: "0,2",
|
||||
page: pageIndex + 1,
|
||||
size: PAGE_SIZE,
|
||||
};
|
||||
@@ -385,13 +384,13 @@ export function VerificationResultHistoryDialog({
|
||||
fcltNm: item.fcltNm ?? "-",
|
||||
vrfcTrgtNm: item.vrfcTrgtNm,
|
||||
rsnExpln: item.rsnExpln,
|
||||
nowVlCn: item.nowVlCn ?? "-",
|
||||
compVlCn: item.compVlCn ?? "-",
|
||||
prcsYnNm: item.prcsYnNm ?? "-",
|
||||
stVrfRjtComNm: item.stVrfRjtComNm ?? "-",
|
||||
nowCn: item.nowCn ?? "-",
|
||||
cmprCn: item.cmprCn ?? "-",
|
||||
vrfcSttsNm: item.vrfcSttsNm ?? "-",
|
||||
statsVrfcRjtComNm: item.statsVrfcRjtComNm ?? "-",
|
||||
prcsOpnnExpln: item.prcsOpnnExpln ?? "-",
|
||||
rjctOpnnCn: item.rjctOpnnCn ?? "-",
|
||||
prcsYn: item.prcsYn ?? "",
|
||||
vrfcSttsCd: item.vrfcSttsCd ?? "",
|
||||
regDt: (item.regDt ?? "").slice(0, 16),
|
||||
mdfcnDt: (item.mdfcnDt ?? "").slice(0, 16),
|
||||
};
|
||||
@@ -434,10 +433,10 @@ export function VerificationResultHistoryDialog({
|
||||
}
|
||||
setCheckedVrfcByTab((prev) => ({ ...prev, [tab]: syncedMap }));
|
||||
const vrfcSnList = Array.from(syncedMap.keys());
|
||||
let mdfrId: string;
|
||||
let mdfrUuid: string;
|
||||
try {
|
||||
const userInfo = await getUserInfo();
|
||||
mdfrId = userInfo.userUnqId;
|
||||
mdfrUuid = userInfo.userUnqId;
|
||||
} catch {
|
||||
alert("사용자 정보를 불러오지 못했습니다.");
|
||||
return;
|
||||
@@ -449,7 +448,7 @@ export function VerificationResultHistoryDialog({
|
||||
statsYr: stableQueryContext.statsYr,
|
||||
sewerBzmnCd: stableQueryContext.sewerBzmnCd,
|
||||
statsCd: stableQueryContext.statsCd,
|
||||
mdfrId,
|
||||
mdfrUuid,
|
||||
vrfcSnList,
|
||||
});
|
||||
if (response.status !== "success") {
|
||||
@@ -512,13 +511,13 @@ export function VerificationResultHistoryDialog({
|
||||
if (!entry) return;
|
||||
const vrfcSn = Number(entry.vrfcSn);
|
||||
if (!Number.isFinite(vrfcSn)) return;
|
||||
if (checked && !canExceptionReset(entry.prcsYn)) {
|
||||
if (checked && !canExceptionReset(entry.vrfcSttsCd)) {
|
||||
alert(EXCEPTION_RESET_ONLY_PRCS_YN_2_MESSAGE);
|
||||
uncheckGridRow(gridRef, itemIndex);
|
||||
return;
|
||||
}
|
||||
updateCheckedVrfcForTab(tab, (map) => {
|
||||
if (checked) map.set(vrfcSn, String(entry.prcsYn ?? ""));
|
||||
if (checked) map.set(vrfcSn, String(entry.vrfcSttsCd ?? ""));
|
||||
else map.delete(vrfcSn);
|
||||
});
|
||||
}}
|
||||
@@ -531,14 +530,14 @@ export function VerificationResultHistoryDialog({
|
||||
map.delete(vrfcSn);
|
||||
return;
|
||||
}
|
||||
if (canExceptionReset(entry.prcsYn)) {
|
||||
map.set(vrfcSn, String(entry.prcsYn ?? ""));
|
||||
if (canExceptionReset(entry.vrfcSttsCd)) {
|
||||
map.set(vrfcSn, String(entry.vrfcSttsCd ?? ""));
|
||||
} else {
|
||||
uncheckGridRow(gridRef, itemIndex);
|
||||
}
|
||||
});
|
||||
});
|
||||
if (checked && entries.some((e) => !canExceptionReset(e.prcsYn))) {
|
||||
if (checked && entries.some((e) => !canExceptionReset(e.vrfcSttsCd))) {
|
||||
alert(EXCEPTION_RESET_ONLY_PRCS_YN_2_MESSAGE);
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -22,7 +22,7 @@ function responseHasVerificationResults(response: VerificationResultListResponse
|
||||
}
|
||||
|
||||
/**
|
||||
* 제출 직후 `VerificationResultDialog`에 표시할 미통과(prcsYn=0) 검증 결과가
|
||||
* 제출 직후 `VerificationResultDialog`에 표시할 미통과(vrfcSttsCd=0) 검증 결과가
|
||||
* 일반검증(NV)·I-MR(IMR)·HB 중 하나라도 있는지 조회합니다.
|
||||
*/
|
||||
export async function hasAnyPostSubmitVerificationResult(
|
||||
@@ -34,8 +34,7 @@ export async function hasAnyPostSubmitVerificationResult(
|
||||
sggNm: ctx.sggNm,
|
||||
statsYr: ctx.statsYr,
|
||||
statsCd: ctx.statsCd,
|
||||
prcsYnList: "0",
|
||||
rejectCodeList: "0,1,2",
|
||||
vrfcSttsCdList: "0",
|
||||
page: 1,
|
||||
size: 1,
|
||||
};
|
||||
|
||||
@@ -379,8 +379,8 @@ const VerificationSelectionReport = ({
|
||||
<div className="flex justify-between text-lg text-muted-foreground">
|
||||
<span>{formatReportItemRegion(item)}</span>
|
||||
<span>
|
||||
현재: {item.nowVlCn ?? "-"} / 비교:{" "}
|
||||
{item.compVlCn ?? "-"}
|
||||
현재: {item.nowCn ?? "-"} / 비교:{" "}
|
||||
{item.cmprCn ?? "-"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -93,8 +93,8 @@ const computeTabEnterable = (
|
||||
statusInfo: SewerageStatusInfo | null | undefined,
|
||||
periodEnterable: boolean,
|
||||
): boolean => {
|
||||
if (statusInfo?.laeSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.laeSbmsnSttsCd)) {
|
||||
if (statusInfo?.lclgvSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.lclgvSbmsnSttsCd)) {
|
||||
return false;
|
||||
}
|
||||
return periodEnterable;
|
||||
@@ -130,7 +130,7 @@ function computeTabToolbarSnapshot(
|
||||
): TabToolbarSnapshot {
|
||||
const gugunOk = buildGugunOkForToolbar(searchContext);
|
||||
const enterable = computeTabEnterable(statusInfo, periodEnterable);
|
||||
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2";
|
||||
const isSubmitted = statusInfo?.lclgvSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
|
||||
const showEnterableToolbarButtons = enterable && gugunOk;
|
||||
const showSubmittedInInputPeriodToolbar =
|
||||
@@ -257,35 +257,35 @@ function buildExcretaTreatmentFacilitySaveItem(
|
||||
fcltSn: nullableTrimmedString(row.fcltSn),
|
||||
fcltNm: nullableTrimmedString(row.fcltNm),
|
||||
fcltPstnCn: nullableTrimmedString(row.fcltPstnCn),
|
||||
telNo: nullableTrimmedString(row.telNo),
|
||||
telnoCn: nullableTrimmedString(row.telnoCn),
|
||||
cmptnStrtYmd: toApiYmd(row.cmptnStrtYmd),
|
||||
oprtngStrtYmd: toApiYmd(row.oprtngStrtYmd),
|
||||
wholPjtcoAmt: toGridNumber(row.wholPjtcoAmt),
|
||||
prcsFcltCpct: toGridNumber(row.prcsFcltCpct),
|
||||
cpct: toGridNumber(row.cpct),
|
||||
oprtngDayCnt: toGridNumber(row.oprtngDayCnt),
|
||||
prcsMthdExpln: nullableTrimmedString(row.prcsMthdExpln),
|
||||
fcltCpct: toGridNumber(row.fcltCpct),
|
||||
excrFcltPrcqty: toGridNumber(row.excrFcltPrcqty),
|
||||
fcltOprtngDayCnt: toGridNumber(row.fcltOprtngDayCnt),
|
||||
prcsExpln: nullableTrimmedString(row.prcsExpln),
|
||||
linkTmplntCn: nullableTrimmedString(row.linkTmplntCn),
|
||||
linkPrcsQnt: toGridNumber(row.linkPrcsQnt),
|
||||
inflwBodWqVl: toGridNumber(row.inflwBodWqVl),
|
||||
inflwTocWqVl: toGridNumber(row.inflwTocWqVl),
|
||||
inflwSsWqVl: toGridNumber(row.inflwSsWqVl),
|
||||
inflwTnWqVl: toGridNumber(row.inflwTnWqVl),
|
||||
inflwTpWqVl: toGridNumber(row.inflwTpWqVl),
|
||||
inflwClfgCnt: toGridNumber(row.inflwClfgCnt),
|
||||
dschgBodWqVl: toGridNumber(row.dschgBodWqVl),
|
||||
dschgTocWqVl: toGridNumber(row.dschgTocWqVl),
|
||||
dschgSsWqVl: toGridNumber(row.dschgSsWqVl),
|
||||
dschgTnWqVl: toGridNumber(row.dschgTnWqVl),
|
||||
dschgTpWqVl: toGridNumber(row.dschgTpWqVl),
|
||||
dschgClfgCnt: toGridNumber(row.dschgClfgCnt),
|
||||
wtsysNm: nullableTrimmedString(row.wtsysNm),
|
||||
trbtrNm: nullableTrimmedString(row.trbtrNm),
|
||||
inflwWqBod: toGridNumber(row.inflwWqBod),
|
||||
inflwWqTorgcb: toGridNumber(row.inflwWqTorgcb),
|
||||
inflwWqSpsld: toGridNumber(row.inflwWqSpsld),
|
||||
inflwWqDtn: toGridNumber(row.inflwWqDtn),
|
||||
inflwWqTph: toGridNumber(row.inflwWqTph),
|
||||
fcltInflwClfgCnt: toGridNumber(row.fcltInflwClfgCnt),
|
||||
dschgWqBod: toGridNumber(row.dschgWqBod),
|
||||
dschgWqTorgcb: toGridNumber(row.dschgWqTorgcb),
|
||||
dschgWqSpsld: toGridNumber(row.dschgWqSpsld),
|
||||
dschgWqDtn: toGridNumber(row.dschgWqDtn),
|
||||
dschgWqTph: toGridNumber(row.dschgWqTph),
|
||||
fcltDschgClfgCnt: toGridNumber(row.fcltDschgClfgCnt),
|
||||
fcltWtsysNm: nullableTrimmedString(row.fcltWtsysNm),
|
||||
fcltTrbtrNm: nullableTrimmedString(row.fcltTrbtrNm),
|
||||
msirbCn: nullableTrimmedString(row.msirbCn),
|
||||
ssirbCn: nullableTrimmedString(row.ssirbCn),
|
||||
operMthdCdCn: nullableTrimmedString(row.operMthdCdCn),
|
||||
operCn: nullableTrimmedString(row.operCn),
|
||||
wholNope: toGridNumber(row.wholNope),
|
||||
sftyAcdntEvntcsCnt: toGridNumber(row.sftyAcdntEvntcsCnt),
|
||||
sftyAcdntNocs: toGridNumber(row.sftyAcdntNocs),
|
||||
rmrk: nullableTrimmedString(row.rmrk),
|
||||
};
|
||||
}
|
||||
@@ -615,7 +615,7 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
mdfrId: userInfo.userUnqId,
|
||||
mdfrUuid: userInfo.userUnqId,
|
||||
statsCd: statsCd || EXCRETA_TREATMENT_FACILITY_STATS_CD,
|
||||
};
|
||||
|
||||
@@ -674,7 +674,7 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
||||
noApplicable,
|
||||
saveList,
|
||||
@@ -775,16 +775,15 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
userId: userInfo.userUnqId,
|
||||
laePicNm: submitInfo.laePicNm,
|
||||
laePicTelNo: submitInfo.laePicTelNo,
|
||||
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm,
|
||||
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm,
|
||||
laeIdfrNm: submitInfo.laeIdfrNm,
|
||||
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
|
||||
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
|
||||
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
||||
userUuid: userInfo.userUnqId,
|
||||
lclgvPicNm: submitInfo.lclgvPicNm,
|
||||
lclgvPicTelnoCn: submitInfo.lclgvPicTelnoCn,
|
||||
lclgvKbrdrDeptNm: submitInfo.lclgvKbrdrDeptNm,
|
||||
lclgvKbrdrJbgdNm: submitInfo.lclgvKbrdrJbgdNm,
|
||||
lclgvIdfrNm: submitInfo.lclgvIdfrNm,
|
||||
lclgvIdfrTelnoCn: submitInfo.lclgvIdfrTelnoCn,
|
||||
lclgvIdfrDeptNm: submitInfo.lclgvIdfrDeptNm,
|
||||
lclgvIdfrJbgdNm: submitInfo.lclgvIdfrJbgdNm,
|
||||
noApplicable,
|
||||
submitList,
|
||||
deleteList: deleteListForSubmit,
|
||||
@@ -808,7 +807,7 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
stats_cd: statsCdForSubmit,
|
||||
stats_yr: lastSearchedValues.year ?? "",
|
||||
lae_sbmsn_stts_cd:
|
||||
statusInfo?.laeSbmsnSttsCd === "4" ? "4" : "2",
|
||||
statusInfo?.lclgvSbmsnSttsCd === "4" ? "4" : "2",
|
||||
gen_verify_stts_cd: nvpassed ? "1" : "2",
|
||||
hb_pass_yn: response.data?.isHbPassed,
|
||||
};
|
||||
@@ -962,7 +961,7 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
? "일반검증 - "
|
||||
: "모집단 검증 -";
|
||||
return `${prefix}${e.rsnExpln}
|
||||
(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`;
|
||||
(검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
@@ -1044,7 +1043,7 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
{submitOk && <SubmitButton submitFunc={submitFunc} />}
|
||||
</>
|
||||
)}
|
||||
{statusInfo?.laeSbmsnSttsCd === "2" && submitOk && (
|
||||
{statusInfo?.lclgvSbmsnSttsCd === "2" && submitOk && (
|
||||
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
|
||||
)}
|
||||
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
|
||||
|
||||
@@ -249,7 +249,7 @@ export function MainFacilityMappingSaveDialog({
|
||||
setIsSubmitting(true);
|
||||
await onConfirm?.({
|
||||
prxyBzentyNmCn: prxyBzentyNmCn.trim(),
|
||||
cnsgnCstVl,
|
||||
cnsgnCstVl: cnsgnCstVl,
|
||||
wholNope,
|
||||
prxyCtrtCn: prxyCtrtCnParam,
|
||||
});
|
||||
|
||||
@@ -119,8 +119,8 @@ const computeTabEnterable = (
|
||||
statusInfo: SewerageStatusInfo | null | undefined,
|
||||
periodEnterable: boolean,
|
||||
): boolean => {
|
||||
if (statusInfo?.laeSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.laeSbmsnSttsCd)) {
|
||||
if (statusInfo?.lclgvSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.lclgvSbmsnSttsCd)) {
|
||||
return false;
|
||||
}
|
||||
return periodEnterable;
|
||||
@@ -148,7 +148,7 @@ function computeTabToolbarSnapshot(
|
||||
): TabToolbarSnapshot {
|
||||
const gugunOk = buildGugunOkForToolbar(searchContext);
|
||||
const enterable = computeTabEnterable(statusInfo, periodEnterable);
|
||||
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2";
|
||||
const isSubmitted = statusInfo?.lclgvSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
|
||||
const showEnterableToolbarButtons = enterable && gugunOk;
|
||||
const showSubmittedInInputPeriodToolbar = isSubmitted && periodEnterable && gugunOk;
|
||||
@@ -210,20 +210,20 @@ function buildSewageTreatFactStatusSaveList(
|
||||
return currentData.map((d) => ({
|
||||
admdstCd: nullableTrimmedString(d.admdstCd),
|
||||
admdstNm: nullableTrimmedString(d.admdstNm),
|
||||
step1StaaPrcsQnt: formatNumber(d.step1StaaPrcsQnt, "step1StaaPrcsQnt"),
|
||||
step2StaaPrcsQnt: formatNumber(d.step2StaaPrcsQnt, "step2StaaPrcsQnt"),
|
||||
step3StaaPrcsQnt: formatNumber(d.step3StaaPrcsQnt, "step3StaaPrcsQnt"),
|
||||
step4StaaPrcsQnt: formatNumber(d.step4StaaPrcsQnt, "step4StaaPrcsQnt"),
|
||||
step5StaaPrcsQnt: formatNumber(d.step5StaaPrcsQnt, "step5StaaPrcsQnt"),
|
||||
step6StaaPrcsQnt: formatNumber(d.step6StaaPrcsQnt, "step6StaaPrcsQnt"),
|
||||
step7StaaPrcsQnt: formatNumber(d.step7StaaPrcsQnt, "step7StaaPrcsQnt"),
|
||||
step1StaaExcpPrcsQnt: formatNumber(d.step1StaaExcpPrcsQnt, "step1StaaExcpPrcsQnt"),
|
||||
step2StaaExcpPrcsQnt: formatNumber(d.step2StaaExcpPrcsQnt, "step2StaaExcpPrcsQnt"),
|
||||
step3StaaExcpPrcsQnt: formatNumber(d.step3StaaExcpPrcsQnt, "step3StaaExcpPrcsQnt"),
|
||||
step4StaaExcpPrcsQnt: formatNumber(d.step4StaaExcpPrcsQnt, "step4StaaExcpPrcsQnt"),
|
||||
step5StaaExcpPrcsQnt: formatNumber(d.step5StaaExcpPrcsQnt, "step5StaaExcpPrcsQnt"),
|
||||
step6StaaExcpPrcsQnt: formatNumber(d.step6StaaExcpPrcsQnt, "step6StaaExcpPrcsQnt"),
|
||||
step7StaaExcpPrcsQnt: formatNumber(d.step7StaaExcpPrcsQnt, "step7StaaExcpPrcsQnt"),
|
||||
frlvlStaaPrcqty: formatNumber(d.frlvlStaaPrcqty, "frlvlStaaPrcqty"),
|
||||
sclvlStaaPrcqty: formatNumber(d.sclvlStaaPrcqty, "sclvlStaaPrcqty"),
|
||||
thlvlStaaPrcqty: formatNumber(d.thlvlStaaPrcqty, "thlvlStaaPrcqty"),
|
||||
folvlStaaPrcqty: formatNumber(d.folvlStaaPrcqty, "folvlStaaPrcqty"),
|
||||
filvlStaaPrcqty: formatNumber(d.filvlStaaPrcqty, "filvlStaaPrcqty"),
|
||||
sxlvlStaaPrcqty: formatNumber(d.sxlvlStaaPrcqty, "sxlvlStaaPrcqty"),
|
||||
svlvlStaaPrcqty: formatNumber(d.svlvlStaaPrcqty, "svlvlStaaPrcqty"),
|
||||
frlvlStaaOtsdPrcqty: formatNumber(d.frlvlStaaOtsdPrcqty, "frlvlStaaOtsdPrcqty"),
|
||||
sclvlStaaOtsdPrcqty: formatNumber(d.sclvlStaaOtsdPrcqty, "sclvlStaaOtsdPrcqty"),
|
||||
thlvlStaaOtsdPrcqty: formatNumber(d.thlvlStaaOtsdPrcqty, "thlvlStaaOtsdPrcqty"),
|
||||
folvlStaaOtsdPrcqty: formatNumber(d.folvlStaaOtsdPrcqty, "folvlStaaOtsdPrcqty"),
|
||||
filvlStaaOtsdPrcqty: formatNumber(d.filvlStaaOtsdPrcqty, "filvlStaaOtsdPrcqty"),
|
||||
sxlvlStaaOtsdPrcqty: formatNumber(d.sxlvlStaaOtsdPrcqty, "sxlvlStaaOtsdPrcqty"),
|
||||
svlvlStaaOtsdPrcqty: formatNumber(d.svlvlStaaOtsdPrcqty, "svlvlStaaOtsdPrcqty"),
|
||||
rmrk: nullableTrimmedString(d.rmrk),
|
||||
}));
|
||||
}
|
||||
@@ -236,34 +236,34 @@ function buildSepticTankStatusSaveList(
|
||||
return currentData.map((d) => ({
|
||||
admdstCd: nullableTrimmedString(d.admdstCd),
|
||||
admdstNm: nullableTrimmedString(d.admdstNm),
|
||||
staaInsdSttkNprsflCnt: formatNumber(d.staaInsdSttkNprsflCnt, "staaInsdSttkNprsflCnt"),
|
||||
step1StaaInsdSttkFrcCnt: formatNumber(d.step1StaaInsdSttkFrcCnt, "step1StaaInsdSttkFrcCnt"),
|
||||
staaInsdSprktpFltrtbCnt: formatNumber(d.staaInsdSprktpFltrtbCnt, "staaInsdSprktpFltrtbCnt"),
|
||||
staaInsdAertMthdCnt: formatNumber(d.staaInsdAertMthdCnt, "staaInsdAertMthdCnt"),
|
||||
staaInsdCntcAertCnt: formatNumber(d.staaInsdCntcAertCnt, "staaInsdCntcAertCnt"),
|
||||
staaInsdSpctnkNatrCnt: formatNumber(d.staaInsdSpctnkNatrCnt, "staaInsdSpctnkNatrCnt"),
|
||||
staaInsdSpctnkFrcCnt: formatNumber(d.staaInsdSpctnkFrcCnt, "staaInsdSpctnkFrcCnt"),
|
||||
staaInsdSprklFiltrCnt: formatNumber(d.staaInsdSprklFiltrCnt, "staaInsdSprklFiltrCnt"),
|
||||
staaInsdAirCnt: formatNumber(d.staaInsdAirCnt, "staaInsdAirCnt"),
|
||||
staaInsdCntcAirCnt: formatNumber(d.staaInsdCntcAirCnt, "staaInsdCntcAirCnt"),
|
||||
staaInsdEtcCnt: formatNumber(d.staaInsdEtcCnt, "staaInsdEtcCnt"),
|
||||
step1StaaInsdNope: formatNumber(d.step1StaaInsdNope, "step1StaaInsdNope"),
|
||||
step2StaaInsdNope: formatNumber(d.step2StaaInsdNope, "step2StaaInsdNope"),
|
||||
step3StaaInsdNope: formatNumber(d.step3StaaInsdNope, "step3StaaInsdNope"),
|
||||
step4StaaInsdNope: formatNumber(d.step4StaaInsdNope, "step4StaaInsdNope"),
|
||||
step5StaaInsdNope: formatNumber(d.step5StaaInsdNope, "step5StaaInsdNope"),
|
||||
step6StaaInsdNope: formatNumber(d.step6StaaInsdNope, "step6StaaInsdNope"),
|
||||
step7StaaInsdNope: formatNumber(d.step7StaaInsdNope, "step7StaaInsdNope"),
|
||||
step8StaaInsdNope: formatNumber(d.step8StaaInsdNope, "step8StaaInsdNope"),
|
||||
staaExcpSttkNprsflCnt: formatNumber(d.staaExcpSttkNprsflCnt, "staaExcpSttkNprsflCnt"),
|
||||
step1StaaExcpSttkFrcCnt: formatNumber(d.step1StaaExcpSttkFrcCnt, "step1StaaExcpSttkFrcCnt"),
|
||||
staaExcpSprktpFltrtbCnt: formatNumber(d.staaExcpSprktpFltrtbCnt, "staaExcpSprktpFltrtbCnt"),
|
||||
staaExcpAertMthdCnt: formatNumber(d.staaExcpAertMthdCnt, "staaExcpAertMthdCnt"),
|
||||
staaExcpCntcAertCnt: formatNumber(d.staaExcpCntcAertCnt, "staaExcpCntcAertCnt"),
|
||||
staaExcpEtcCnt: formatNumber(d.staaExcpEtcCnt, "staaExcpEtcCnt"),
|
||||
step1StaaExcpNope: formatNumber(d.step1StaaExcpNope, "step1StaaExcpNope"),
|
||||
step2StaaExcpNope: formatNumber(d.step2StaaExcpNope, "step2StaaExcpNope"),
|
||||
step3StaaExcpNope: formatNumber(d.step3StaaExcpNope, "step3StaaExcpNope"),
|
||||
step4StaaExcpNope: formatNumber(d.step4StaaExcpNope, "step4StaaExcpNope"),
|
||||
step5StaaExcpNope: formatNumber(d.step5StaaExcpNope, "step5StaaExcpNope"),
|
||||
step6StaaExcpNope: formatNumber(d.step6StaaExcpNope, "step6StaaExcpNope"),
|
||||
step7StaaExcpNope: formatNumber(d.step7StaaExcpNope, "step7StaaExcpNope"),
|
||||
step8StaaExcpNope: formatNumber(d.step8StaaExcpNope, "step8StaaExcpNope"),
|
||||
frlvlStaaInsdNope: formatNumber(d.frlvlStaaInsdNope, "frlvlStaaInsdNope"),
|
||||
sclvlStaaInsdNope: formatNumber(d.sclvlStaaInsdNope, "sclvlStaaInsdNope"),
|
||||
thlvlStaaInsdNope: formatNumber(d.thlvlStaaInsdNope, "thlvlStaaInsdNope"),
|
||||
folvlStaaInsdNope: formatNumber(d.folvlStaaInsdNope, "folvlStaaInsdNope"),
|
||||
filvlStaaInsdNope: formatNumber(d.filvlStaaInsdNope, "filvlStaaInsdNope"),
|
||||
sxlvlStaaInsdNope: formatNumber(d.sxlvlStaaInsdNope, "sxlvlStaaInsdNope"),
|
||||
svlvlStaaInsdNope: formatNumber(d.svlvlStaaInsdNope, "svlvlStaaInsdNope"),
|
||||
eilvlStaaInsdNope: formatNumber(d.eilvlStaaInsdNope, "eilvlStaaInsdNope"),
|
||||
staaOtsdSpctnkNatrCnt: formatNumber(d.staaOtsdSpctnkNatrCnt, "staaOtsdSpctnkNatrCnt"),
|
||||
staaOtsdSpctnkFrcCnt: formatNumber(d.staaOtsdSpctnkFrcCnt, "staaOtsdSpctnkFrcCnt"),
|
||||
staaOtsdSprklFiltrCnt: formatNumber(d.staaOtsdSprklFiltrCnt, "staaOtsdSprklFiltrCnt"),
|
||||
staaOtsdAirCnt: formatNumber(d.staaOtsdAirCnt, "staaOtsdAirCnt"),
|
||||
staaOtsdCntcAirCnt: formatNumber(d.staaOtsdCntcAirCnt, "staaOtsdCntcAirCnt"),
|
||||
staaOtsdEtcCnt: formatNumber(d.staaOtsdEtcCnt, "staaOtsdEtcCnt"),
|
||||
frlvlStaaOtsdNope: formatNumber(d.frlvlStaaOtsdNope, "frlvlStaaOtsdNope"),
|
||||
sclvlStaaOtsdNope: formatNumber(d.sclvlStaaOtsdNope, "sclvlStaaOtsdNope"),
|
||||
thlvlStaaOtsdNope: formatNumber(d.thlvlStaaOtsdNope, "thlvlStaaOtsdNope"),
|
||||
folvlStaaOtsdNope: formatNumber(d.folvlStaaOtsdNope, "folvlStaaOtsdNope"),
|
||||
filvlStaaOtsdNope: formatNumber(d.filvlStaaOtsdNope, "filvlStaaOtsdNope"),
|
||||
sxlvlStaaOtsdNope: formatNumber(d.sxlvlStaaOtsdNope, "sxlvlStaaOtsdNope"),
|
||||
svlvlStaaOtsdNope: formatNumber(d.svlvlStaaOtsdNope, "svlvlStaaOtsdNope"),
|
||||
eilvlStaaOtsdNope: formatNumber(d.eilvlStaaOtsdNope, "eilvlStaaOtsdNope"),
|
||||
rmrk: nullableTrimmedString(d.rmrk),
|
||||
}));
|
||||
}
|
||||
@@ -278,13 +278,13 @@ function buildPrivateSewageDesigStatusSaveList(
|
||||
admdstNm: nullableTrimmedString(d.admdstNm),
|
||||
indvSwagePbancRgnCnt: formatNumber(d.indvSwagePbancRgnCnt, "indvSwagePbancRgnCnt"),
|
||||
indvSwageDsgnScpLen: formatNumber(d.indvSwageDsgnScpLen, "indvSwageDsgnScpLen"),
|
||||
spctnkGrssFcltCnt: formatNumber(d.spctnkGrssFcltCnt, "spctnkGrssFcltCnt"),
|
||||
spctnkQnt: formatNumber(d.spctnkQnt, "spctnkQnt"),
|
||||
drtwPrcsFcltGrssFcltCnt: formatNumber(
|
||||
d.drtwPrcsFcltGrssFcltCnt,
|
||||
"drtwPrcsFcltGrssFcltCnt",
|
||||
spctnkWholFcltCnt: formatNumber(d.spctnkWholFcltCnt, "spctnkWholFcltCnt"),
|
||||
spctnkPrcqty: formatNumber(d.spctnkPrcqty, "spctnkPrcqty"),
|
||||
swgdplFcltWholFcltCnt: formatNumber(
|
||||
d.swgdplFcltWholFcltCnt,
|
||||
"swgdplFcltWholFcltCnt",
|
||||
),
|
||||
drtwPrcsFcltQnt: formatNumber(d.drtwPrcsFcltQnt, "drtwPrcsFcltQnt"),
|
||||
swgdplFcltPrcqty: formatNumber(d.swgdplFcltPrcqty, "swgdplFcltPrcqty"),
|
||||
rmrk: nullableTrimmedString(d.rmrk),
|
||||
}));
|
||||
}
|
||||
@@ -721,7 +721,7 @@ const PrivateSewageFacilityInp = () => {
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
mdfrId: userInfo.userUnqId,
|
||||
mdfrUuid: userInfo.userUnqId,
|
||||
statsCd: statsCdForTab,
|
||||
};
|
||||
|
||||
@@ -797,20 +797,19 @@ const PrivateSewageFacilityInp = () => {
|
||||
|
||||
const activeTabStatus = tabStatusInfoMap.get(tabKey) ?? null;
|
||||
const statsCdForTab = tabStatsCdMap.get(tabKey) ?? tabKey;
|
||||
const adminOpt = isAdminInfo ? { superAdmin: "superAdmin" as const } : {};
|
||||
|
||||
const submitInfoBase = {
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
laePicNm: submitInfo.laePicNm,
|
||||
laePicTelNo: submitInfo.laePicTelNo,
|
||||
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm,
|
||||
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm,
|
||||
laeIdfrNm: submitInfo.laeIdfrNm,
|
||||
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
|
||||
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
|
||||
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
|
||||
lclgvPicNm: submitInfo.lclgvPicNm,
|
||||
lclgvPicTelnoCn: submitInfo.lclgvPicTelnoCn,
|
||||
lclgvKbrdrDeptNm: submitInfo.lclgvKbrdrDeptNm,
|
||||
lclgvKbrdrJbgdNm: submitInfo.lclgvKbrdrJbgdNm,
|
||||
lclgvIdfrNm: submitInfo.lclgvIdfrNm,
|
||||
lclgvIdfrTelnoCn: submitInfo.lclgvIdfrTelnoCn,
|
||||
lclgvIdfrDeptNm: submitInfo.lclgvIdfrDeptNm,
|
||||
lclgvIdfrJbgdNm: submitInfo.lclgvIdfrJbgdNm,
|
||||
};
|
||||
|
||||
const verificationQuery = {
|
||||
@@ -840,9 +839,8 @@ const PrivateSewageFacilityInp = () => {
|
||||
if (isSewageTreatTab) {
|
||||
const param: SewageTreatFactStatusSubmitRequest = {
|
||||
...submitInfoBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: buildSewageTreatFactStatusSaveList(currentData),
|
||||
...adminOpt,
|
||||
};
|
||||
response =
|
||||
await statisticsInputPrivateSewageFacilityApi.submitSewageTreatFactStatus(
|
||||
@@ -851,18 +849,16 @@ const PrivateSewageFacilityInp = () => {
|
||||
} else if (isSepticTankTab) {
|
||||
const param: SepticTankStatusSubmitRequest = {
|
||||
...submitInfoBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: buildSepticTankStatusSaveList(currentData),
|
||||
...adminOpt,
|
||||
};
|
||||
response =
|
||||
await statisticsInputPrivateSewageFacilityApi.submitSepticTankStatus(param);
|
||||
} else {
|
||||
const param: PrivateSewageDesigStatusSubmitRequest = {
|
||||
...submitInfoBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: buildPrivateSewageDesigStatusSaveList(currentData),
|
||||
...adminOpt,
|
||||
};
|
||||
response =
|
||||
await statisticsInputPrivateSewageFacilityApi.submitPrivateSewageDesigStatus(
|
||||
@@ -886,7 +882,7 @@ const PrivateSewageFacilityInp = () => {
|
||||
sewer_bzmn_cd: lastSearchedValues?.bsnmCd ?? "",
|
||||
stats_cd: statsCdForTab,
|
||||
stats_yr: lastSearchedValues.year ?? "",
|
||||
lae_sbmsn_stts_cd: activeTabStatus?.laeSbmsnSttsCd === "4" ? "4" : "2",
|
||||
lae_sbmsn_stts_cd: activeTabStatus?.lclgvSbmsnSttsCd === "4" ? "4" : "2",
|
||||
gen_verify_stts_cd: nvpassed ? "1" : "2",
|
||||
hb_pass_yn: response.data?.isHbPassed,
|
||||
};
|
||||
@@ -982,7 +978,7 @@ const PrivateSewageFacilityInp = () => {
|
||||
if (isSewageTreatTab) {
|
||||
const param: SewageTreatFactStatusSaveRequest = {
|
||||
...baseRequest,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
saveList: buildSewageTreatFactStatusSaveList(currentData),
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
||||
};
|
||||
@@ -1008,7 +1004,7 @@ const PrivateSewageFacilityInp = () => {
|
||||
if (isSepticTankTab) {
|
||||
const param: SepticTankStatusSaveRequest = {
|
||||
...baseRequest,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
saveList: buildSepticTankStatusSaveList(currentData),
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
||||
};
|
||||
@@ -1031,7 +1027,7 @@ const PrivateSewageFacilityInp = () => {
|
||||
|
||||
const param: PrivateSewageDesigStatusSaveRequest = {
|
||||
...baseRequest,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
saveList: buildPrivateSewageDesigStatusSaveList(currentData),
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
||||
};
|
||||
@@ -1156,7 +1152,7 @@ const PrivateSewageFacilityInp = () => {
|
||||
: e.vrfcCd === "NV"
|
||||
? "일반검증 - "
|
||||
: "모집단 검증 - ";
|
||||
return `${prefix}${e.rsnExpln}(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`;
|
||||
return `${prefix}${e.rsnExpln}(검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
@@ -1202,7 +1198,7 @@ const PrivateSewageFacilityInp = () => {
|
||||
{submitOk && <SubmitButton submitFunc={submitFunc} />}
|
||||
</>
|
||||
)}
|
||||
{activeTabStatus?.laeSbmsnSttsCd === "2" && submitOk && (
|
||||
{activeTabStatus?.lclgvSbmsnSttsCd === "2" && submitOk && (
|
||||
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
|
||||
)}
|
||||
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type Dispatch, type SetStateAction,useEffect, useRef, useState } from 'react';
|
||||
import { type Dispatch, type SetStateAction,useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { useConfirm } from '@frontend-opensource/use-react-hooks';
|
||||
|
||||
@@ -89,8 +89,8 @@ const computeTabEnterable = (
|
||||
statusInfo: SewerageStatusInfo | null | undefined,
|
||||
periodEnterable: boolean,
|
||||
): boolean => {
|
||||
if (statusInfo?.laeSbmsnSttsCd) {
|
||||
if (['2', '3'].includes(statusInfo.laeSbmsnSttsCd)) {
|
||||
if (statusInfo?.lclgvSbmsnSttsCd) {
|
||||
if (['2', '3'].includes(statusInfo.lclgvSbmsnSttsCd)) {
|
||||
return false;
|
||||
}
|
||||
return periodEnterable;
|
||||
@@ -108,7 +108,7 @@ function computeTabToolbarSnapshot(
|
||||
): TabToolbarSnapshot {
|
||||
const gugunOk = buildGugunOkForToolbar(searchContext);
|
||||
const enterable = computeTabEnterable(statusInfo, periodEnterable);
|
||||
const isSubmitted = statusInfo?.laeSbmsnSttsCd === '2';
|
||||
const isSubmitted = statusInfo?.lclgvSbmsnSttsCd === '2';
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
|
||||
const showEnterableToolbarButtons = enterable && gugunOk;
|
||||
const showSubmittedInInputPeriodToolbar = isSubmitted && periodEnterable && gugunOk;
|
||||
@@ -397,16 +397,16 @@ const PublicSewageFacilityInp = () => {
|
||||
statsYr: lastSearchedValues.year ?? '',
|
||||
ctpvNm: lastSearchedValues.sido ?? '',
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
noApplicable,
|
||||
laePicNm: submitInfo.laePicNm,
|
||||
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm,
|
||||
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm,
|
||||
laePicTelNo: submitInfo.laePicTelNo,
|
||||
laeIdfrNm: submitInfo.laeIdfrNm,
|
||||
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
|
||||
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
|
||||
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
|
||||
lclgvPicNm: submitInfo.lclgvPicNm,
|
||||
lclgvKbrdrJbgdNm: submitInfo.lclgvKbrdrJbgdNm,
|
||||
lclgvKbrdrDeptNm: submitInfo.lclgvKbrdrDeptNm,
|
||||
lclgvPicTelnoCn: submitInfo.lclgvPicTelnoCn,
|
||||
lclgvIdfrNm: submitInfo.lclgvIdfrNm,
|
||||
lclgvIdfrJbgdNm: submitInfo.lclgvIdfrJbgdNm,
|
||||
lclgvIdfrDeptNm: submitInfo.lclgvIdfrDeptNm,
|
||||
lclgvIdfrTelnoCn: submitInfo.lclgvIdfrTelnoCn,
|
||||
submitList,
|
||||
};
|
||||
|
||||
@@ -432,7 +432,7 @@ const PublicSewageFacilityInp = () => {
|
||||
sewer_bzmn_cd: lastSearchedValues?.bsnmCd ?? '',
|
||||
stats_cd: statsCdForTab,
|
||||
stats_yr: lastSearchedValues.year ?? '',
|
||||
lae_sbmsn_stts_cd: activeTabStatus?.laeSbmsnSttsCd === '4' ? '4' : '2',
|
||||
lae_sbmsn_stts_cd: activeTabStatus?.lclgvSbmsnSttsCd === '4' ? '4' : '2',
|
||||
gen_verify_stts_cd: nvpassed ? '1' : '2',
|
||||
hb_pass_yn: response.data?.isHbPassed,
|
||||
};
|
||||
@@ -657,7 +657,7 @@ const PublicSewageFacilityInp = () => {
|
||||
statsYr: lastSearchedValues.year ?? '',
|
||||
ctpvNm: lastSearchedValues.sido ?? '',
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
...(isAdminInfo ? { superAdmin: 'superAdmin' } : {}),
|
||||
noApplicable,
|
||||
saveList,
|
||||
@@ -902,7 +902,7 @@ const PublicSewageFacilityInp = () => {
|
||||
statsYr: lastSearchedValues.year ?? '',
|
||||
ctpvNm: lastSearchedValues.sido ?? '',
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
};
|
||||
|
||||
const response =
|
||||
@@ -949,7 +949,7 @@ const PublicSewageFacilityInp = () => {
|
||||
admdstNm: draft.admdstNm,
|
||||
mainFcltSn: draft.mainFcltSn,
|
||||
mainFcltNm: draft.mainFcltNm,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
saveList: draft.saveList,
|
||||
deleteList: draft.deleteList,
|
||||
};
|
||||
@@ -1128,7 +1128,7 @@ const PublicSewageFacilityInp = () => {
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
ctpvNm: lastSearchedValues?.sido ?? '',
|
||||
statsYr: lastSearchedValues?.year ?? '',
|
||||
mdfrId: userInfo.userUnqId,
|
||||
mdfrUuid: userInfo.userUnqId,
|
||||
statsCd: statsCdForCancel,
|
||||
};
|
||||
|
||||
@@ -1203,15 +1203,17 @@ const PublicSewageFacilityInp = () => {
|
||||
alert('해당 시설은 중심처리시설의 위탁시설로 연결되어있습니다.');
|
||||
}
|
||||
const mappedTargetRows = applyExclusiveSelfMappingChecks(
|
||||
rawList.map((item) => ({
|
||||
...item,
|
||||
__checked: item.mappedYn === 'Y' ? 'Y' : '',
|
||||
})),
|
||||
toGridRows(
|
||||
rawList.map((item) => ({
|
||||
...item,
|
||||
__checked: item.mappedYn === 'Y' ? 'Y' : '',
|
||||
})),
|
||||
),
|
||||
fcltSn,
|
||||
);
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set('MainFacilityTarget', toGridRows(mappedTargetRows));
|
||||
next.set('MainFacilityTarget', mappedTargetRows);
|
||||
return next;
|
||||
});
|
||||
} catch {
|
||||
@@ -1321,7 +1323,7 @@ const PublicSewageFacilityInp = () => {
|
||||
{submitOk && <SubmitButton submitFunc={submitFunc} />}
|
||||
</>
|
||||
)}
|
||||
{!isMainFacilityTab && activeTabStatus?.laeSbmsnSttsCd === '2' && submitOk && (
|
||||
{!isMainFacilityTab && activeTabStatus?.lclgvSbmsnSttsCd === '2' && submitOk && (
|
||||
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
|
||||
)}
|
||||
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
|
||||
@@ -1577,8 +1579,8 @@ const PublicSewageFacilityInp = () => {
|
||||
.map((e) => {
|
||||
const prefix =
|
||||
e.vrfcCd === "IMR" ? "시계열 검증 - " : (e.vrfcCd === "NV" ? "일반검증 - " : "모집단 검증 - ");
|
||||
const comp = e.compVlCn ?? "";
|
||||
const now = e.nowVlCn ?? "";
|
||||
const comp = e.cmprCn ?? "";
|
||||
const now = e.nowCn ?? "";
|
||||
return `${prefix}${e.rsnExpln}
|
||||
(검증 기준값: ${comp} / 현재 입력값: ${now})`;
|
||||
})
|
||||
|
||||
@@ -93,8 +93,8 @@ const computeTabEnterable = (
|
||||
statusInfo: SewerageStatusInfo | null | undefined,
|
||||
periodEnterable: boolean,
|
||||
): boolean => {
|
||||
if (statusInfo?.laeSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.laeSbmsnSttsCd)) {
|
||||
if (statusInfo?.lclgvSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.lclgvSbmsnSttsCd)) {
|
||||
return false;
|
||||
}
|
||||
return periodEnterable;
|
||||
@@ -130,7 +130,7 @@ function computeTabToolbarSnapshot(
|
||||
): TabToolbarSnapshot {
|
||||
const gugunOk = buildGugunOkForToolbar(searchContext);
|
||||
const enterable = computeTabEnterable(statusInfo, periodEnterable);
|
||||
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2";
|
||||
const isSubmitted = statusInfo?.lclgvSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
|
||||
const showEnterableToolbarButtons = enterable && gugunOk;
|
||||
const showSubmittedInInputPeriodToolbar =
|
||||
@@ -252,34 +252,34 @@ function buildPumpingStationSaveItem(
|
||||
pumpSeNm: nullableTrimmedString(row.pumpSeNm),
|
||||
fcltPstnCn: nullableTrimmedString(row.fcltPstnCn),
|
||||
oprtngStrtYmd: toCompactYmdOrNull(row.oprtngStrtYmd),
|
||||
planMltvArea: toGridNumber(row.planMltvArea),
|
||||
sndyArea: toGridNumber(row.sndyArea),
|
||||
planDrnar: toGridNumber(row.planDrnar),
|
||||
icodwArea: toGridNumber(row.icodwArea),
|
||||
rainArea: toGridNumber(row.rainArea),
|
||||
drtwPumpN1DmtrArea: toGridNumber(row.drtwPumpN1DmtrArea),
|
||||
drtwPumpN1Dmtr: toGridNumber(row.drtwPumpN1Dmtr),
|
||||
drtwPumpN1Ppqty: toGridNumber(row.drtwPumpN1Ppqty),
|
||||
drtwPumpN1Cnt: toGridNumber(row.drtwPumpN1Cnt),
|
||||
drtwPumpN1RsrvCnt: toGridNumber(row.drtwPumpN1RsrvCnt),
|
||||
drtwPumpN2DmtrArea: toGridNumber(row.drtwPumpN2DmtrArea),
|
||||
drtwPumpN2Dmtr: toGridNumber(row.drtwPumpN2Dmtr),
|
||||
drtwPumpN2Ppqty: toGridNumber(row.drtwPumpN2Ppqty),
|
||||
drtwPumpN2Cnt: toGridNumber(row.drtwPumpN2Cnt),
|
||||
drtwPumpN2RsrvCnt: toGridNumber(row.drtwPumpN2RsrvCnt),
|
||||
drtwPumpN3DmtrArea: toGridNumber(row.drtwPumpN3DmtrArea),
|
||||
drtwPumpN3Dmtr: toGridNumber(row.drtwPumpN3Dmtr),
|
||||
drtwPumpN3Ppqty: toGridNumber(row.drtwPumpN3Ppqty),
|
||||
drtwPumpN3Cnt: toGridNumber(row.drtwPumpN3Cnt),
|
||||
drtwPumpN3RsrvCnt: toGridNumber(row.drtwPumpN3RsrvCnt),
|
||||
exlnPumpN1DmtrArea: toGridNumber(row.exlnPumpN1DmtrArea),
|
||||
exlnPumpN1Dmtr: toGridNumber(row.exlnPumpN1Dmtr),
|
||||
exlnPumpN1Ppqty: toGridNumber(row.exlnPumpN1Ppqty),
|
||||
exlnPumpN1Cnt: toGridNumber(row.exlnPumpN1Cnt),
|
||||
exlnPumpN1RsrvCnt: toGridNumber(row.exlnPumpN1RsrvCnt),
|
||||
exlnPumpN2DmtrArea: toGridNumber(row.exlnPumpN2DmtrArea),
|
||||
exlnPumpN2Dmtr: toGridNumber(row.exlnPumpN2Dmtr),
|
||||
exlnPumpN2Ppqty: toGridNumber(row.exlnPumpN2Ppqty),
|
||||
exlnPumpN2Cnt: toGridNumber(row.exlnPumpN2Cnt),
|
||||
exlnPumpN2RsrvCnt: toGridNumber(row.exlnPumpN2RsrvCnt),
|
||||
exlnPumpN3DmtrArea: toGridNumber(row.exlnPumpN3DmtrArea),
|
||||
exlnPumpN3Dmtr: toGridNumber(row.exlnPumpN3Dmtr),
|
||||
exlnPumpN3Ppqty: toGridNumber(row.exlnPumpN3Ppqty),
|
||||
exlnPumpN3Cnt: toGridNumber(row.exlnPumpN3Cnt),
|
||||
exlnPumpN3RsrvCnt: toGridNumber(row.exlnPumpN3RsrvCnt),
|
||||
anlElpwrUseQnt: toGridNumber(row.anlElpwrUseQnt),
|
||||
anlElpwrUsqty: toGridNumber(row.anlElpwrUsqty),
|
||||
rmrk: nullableTrimmedString(row.rmrk),
|
||||
};
|
||||
}
|
||||
@@ -606,7 +606,7 @@ const PumpingStationInp = () => {
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
mdfrId: userInfo.userUnqId,
|
||||
mdfrUuid: userInfo.userUnqId,
|
||||
statsCd: statsCd || PUMPING_STATION_STATS_CD,
|
||||
};
|
||||
|
||||
@@ -691,7 +691,7 @@ const PumpingStationInp = () => {
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
||||
noApplicable,
|
||||
saveList,
|
||||
@@ -759,16 +759,15 @@ const PumpingStationInp = () => {
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
userId: userInfo.userUnqId,
|
||||
laePicNm: submitInfo.laePicNm,
|
||||
laePicTelNo: submitInfo.laePicTelNo,
|
||||
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm,
|
||||
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm,
|
||||
laeIdfrNm: submitInfo.laeIdfrNm,
|
||||
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
|
||||
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
|
||||
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
||||
userUuid: userInfo.userUnqId,
|
||||
lclgvPicNm: submitInfo.lclgvPicNm,
|
||||
lclgvPicTelnoCn: submitInfo.lclgvPicTelnoCn,
|
||||
lclgvKbrdrDeptNm: submitInfo.lclgvKbrdrDeptNm,
|
||||
lclgvKbrdrJbgdNm: submitInfo.lclgvKbrdrJbgdNm,
|
||||
lclgvIdfrNm: submitInfo.lclgvIdfrNm,
|
||||
lclgvIdfrTelnoCn: submitInfo.lclgvIdfrTelnoCn,
|
||||
lclgvIdfrDeptNm: submitInfo.lclgvIdfrDeptNm,
|
||||
lclgvIdfrJbgdNm: submitInfo.lclgvIdfrJbgdNm,
|
||||
noApplicable,
|
||||
submitList,
|
||||
deleteList,
|
||||
@@ -791,7 +790,7 @@ const PumpingStationInp = () => {
|
||||
stats_cd: statsCdForSubmit,
|
||||
stats_yr: lastSearchedValues.year ?? "",
|
||||
lae_sbmsn_stts_cd:
|
||||
statusInfo?.laeSbmsnSttsCd === "4" ? "4" : "2",
|
||||
statusInfo?.lclgvSbmsnSttsCd === "4" ? "4" : "2",
|
||||
gen_verify_stts_cd: nvpassed ? "1" : "2",
|
||||
hb_pass_yn: response.data?.isHbPassed,
|
||||
};
|
||||
@@ -945,7 +944,7 @@ const PumpingStationInp = () => {
|
||||
? "일반검증 - "
|
||||
: "모집단 검증 -";
|
||||
return `${prefix}${e.rsnExpln}
|
||||
(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`;
|
||||
(검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
@@ -1027,7 +1026,7 @@ const PumpingStationInp = () => {
|
||||
{submitOk && <SubmitButton submitFunc={submitFunc} />}
|
||||
</>
|
||||
)}
|
||||
{statusInfo?.laeSbmsnSttsCd === "2" && submitOk && (
|
||||
{statusInfo?.lclgvSbmsnSttsCd === "2" && submitOk && (
|
||||
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
|
||||
)}
|
||||
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
|
||||
|
||||
@@ -16,6 +16,7 @@ import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type {
|
||||
ExcretaRelatedIndustrySaveItem,
|
||||
PrivateSewageTreatRelatedIndustrySaveItem,
|
||||
PublicInstitutionExcretaIndustrySaveItem,
|
||||
PublicInstitutionSewerIndustrySaveItem,
|
||||
SewageExcretaRelatedRequestParams,
|
||||
SewageExcretaRelatedVerificationResult,
|
||||
@@ -157,8 +158,8 @@ const computeTabEnterable = (
|
||||
statusInfo: SewerageStatusInfo | null | undefined,
|
||||
periodEnterable: boolean,
|
||||
): boolean => {
|
||||
if (statusInfo?.laeSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.laeSbmsnSttsCd)) {
|
||||
if (statusInfo?.lclgvSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.lclgvSbmsnSttsCd)) {
|
||||
return false;
|
||||
}
|
||||
return periodEnterable;
|
||||
@@ -194,7 +195,7 @@ function computeTabToolbarSnapshot(
|
||||
): TabToolbarSnapshot {
|
||||
const gugunOk = buildGugunOkForToolbar(searchContext);
|
||||
const enterable = computeTabEnterable(statusInfo, periodEnterable);
|
||||
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2";
|
||||
const isSubmitted = statusInfo?.lclgvSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
|
||||
const showEnterableToolbarButtons = enterable && gugunOk;
|
||||
const showSubmittedInInputPeriodToolbar = isSubmitted && periodEnterable && gugunOk;
|
||||
@@ -238,10 +239,20 @@ function isCheckedRow(row: Record<string, string | number>): boolean {
|
||||
|
||||
type ArticleOgnzSnSet = Set<string>;
|
||||
|
||||
function extractOgnzSnSet(rows: Record<string, string | number>[]): ArticleOgnzSnSet {
|
||||
const PUBLIC_INSTITUTION_OGNZ_SN_FIELD = "statsOgnzSn" as const;
|
||||
|
||||
function getPublicInstitutionOgnzSn(
|
||||
row: Record<string, string | number>,
|
||||
): string {
|
||||
return String(row[PUBLIC_INSTITUTION_OGNZ_SN_FIELD] ?? "").trim();
|
||||
}
|
||||
|
||||
function extractOgnzSnSet(
|
||||
rows: Record<string, string | number>[],
|
||||
): ArticleOgnzSnSet {
|
||||
const set = new Set<string>();
|
||||
for (const row of rows) {
|
||||
const ognzSn = String(row.ognzSn ?? "").trim();
|
||||
const ognzSn = getPublicInstitutionOgnzSn(row);
|
||||
if (ognzSn) set.add(ognzSn);
|
||||
}
|
||||
return set;
|
||||
@@ -277,7 +288,7 @@ function isPersistedPublicInstitutionArticleRow(
|
||||
): boolean {
|
||||
if (!ARTICLE_EDIT_TAB_KEYS.has(tabKey)) return false;
|
||||
if (isNewArticleRow(row)) return false;
|
||||
const ognzSn = String(row.ognzSn ?? "").trim();
|
||||
const ognzSn = getPublicInstitutionOgnzSn(row);
|
||||
if (!ognzSn) return false;
|
||||
if ((preYearRef.current.get(tabKey) ?? new Set()).has(ognzSn)) return false;
|
||||
return (persistedRef.current.get(tabKey) ?? new Set()).has(ognzSn);
|
||||
@@ -402,18 +413,18 @@ function toGridNumber(value: string | number | null | undefined): number {
|
||||
function buildPublicInstitutionIndustrySaveItem(
|
||||
row: Record<string, string | number>,
|
||||
sggOptions: Sgg[],
|
||||
): PublicInstitutionSewerIndustrySaveItem {
|
||||
): PublicInstitutionSewerIndustrySaveItem | PublicInstitutionExcretaIndustrySaveItem {
|
||||
const { admdstCd, admdstNm } = resolvePublicInstitutionAdmdst(row, sggOptions);
|
||||
return {
|
||||
admdstCd,
|
||||
admdstNm,
|
||||
ognzSn: nullableTrimmedString(row.ognzSn),
|
||||
statsOgnzSn: nullableTrimmedString(row.statsOgnzSn),
|
||||
ognzNm: nullableTrimmedString(row.ognzNm),
|
||||
pbadmsNope: toGridNumber(row.pbadmsNope),
|
||||
engrkCnt: toGridNumber(row.engrkCnt),
|
||||
mchnCnt: toGridNumber(row.mchnCnt),
|
||||
prvtmCnt: toGridNumber(row.prvtmCnt),
|
||||
chmengCnt: toGridNumber(row.chmengCnt),
|
||||
chemCnt: toGridNumber(row.chemCnt),
|
||||
envCnt: toGridNumber(row.envCnt),
|
||||
enjbEtcCnt: toGridNumber(row.enjbEtcCnt),
|
||||
etcCnt: toGridNumber(row.etcCnt),
|
||||
@@ -430,9 +441,9 @@ function buildExcretaRelatedIndustrySaveItem(
|
||||
admdstNm: nullableTrimmedString(row.admdstNm),
|
||||
bzentyCnt: toGridNumber(row.bzentyCnt),
|
||||
empNope: toGridNumber(row.empNope),
|
||||
n3TonUdrCnt: toGridNumber(row.n3TonUdrCnt),
|
||||
n4TonUdrCnt: toGridNumber(row.n4TonUdrCnt),
|
||||
n8TonUdrCnt: toGridNumber(row.n8TonUdrCnt),
|
||||
frlvlTonUdrCnt: toGridNumber(row.frlvlTonUdrCnt),
|
||||
sclvlTonUdrCnt: toGridNumber(row.sclvlTonUdrCnt),
|
||||
thlvlTonUdrCnt: toGridNumber(row.thlvlTonUdrCnt),
|
||||
etcCnt: toGridNumber(row.etcCnt),
|
||||
clctCnvncPrfmncQnt: toGridNumber(row.clctCnvncPrfmncQnt),
|
||||
rmrk: nullableTrimmedString(row.rmrk),
|
||||
@@ -550,7 +561,7 @@ function verificationCellTooltip({
|
||||
? "일반검증 - "
|
||||
: "모집단 검증 - ";
|
||||
return `${prefix}${e.rsnExpln}
|
||||
(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`;
|
||||
(검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
@@ -1179,7 +1190,7 @@ const SewageExcretaRelatedInp = () => {
|
||||
...toInputSearchRegion(sidoListRef.current, ctx),
|
||||
ctpvNm: ctx.sido ?? "",
|
||||
statsYr: ctx.year ?? "",
|
||||
mdfrId: userInfo.userUnqId,
|
||||
mdfrUuid: userInfo.userUnqId,
|
||||
statsCd: statsCdForTab,
|
||||
};
|
||||
|
||||
@@ -1317,9 +1328,12 @@ const SewageExcretaRelatedInp = () => {
|
||||
|
||||
let submitList:
|
||||
| PublicInstitutionSewerIndustrySaveItem[]
|
||||
| PublicInstitutionExcretaIndustrySaveItem[]
|
||||
| ExcretaRelatedIndustrySaveItem[]
|
||||
| PrivateSewageTreatRelatedIndustrySaveItem[];
|
||||
let deleteList: PublicInstitutionSewerIndustrySaveItem[] = [];
|
||||
let deleteList:
|
||||
| PublicInstitutionSewerIndustrySaveItem[]
|
||||
| PublicInstitutionExcretaIndustrySaveItem[] = [];
|
||||
let sggList: Sgg[] = [];
|
||||
|
||||
if (isDistrictRowTab) {
|
||||
@@ -1351,7 +1365,7 @@ const SewageExcretaRelatedInp = () => {
|
||||
sggList = list;
|
||||
|
||||
const rowsForSubmit = mergeGridRowsWithMapRows(currentData, mapRows, [
|
||||
"ognzSn",
|
||||
PUBLIC_INSTITUTION_OGNZ_SN_FIELD,
|
||||
"admdstCd",
|
||||
"admdstNm",
|
||||
"__isNewArticle",
|
||||
@@ -1387,16 +1401,15 @@ const SewageExcretaRelatedInp = () => {
|
||||
|
||||
const submitRequestBase = {
|
||||
...searchContextBase,
|
||||
laePicNm: submitInfo.laePicNm,
|
||||
laePicTelNo: submitInfo.laePicTelNo,
|
||||
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm,
|
||||
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm,
|
||||
laeIdfrNm: submitInfo.laeIdfrNm,
|
||||
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
|
||||
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
|
||||
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
|
||||
lclgvPicNm: submitInfo.lclgvPicNm,
|
||||
lclgvPicTelnoCn: submitInfo.lclgvPicTelnoCn,
|
||||
lclgvKbrdrDeptNm: submitInfo.lclgvKbrdrDeptNm,
|
||||
lclgvKbrdrJbgdNm: submitInfo.lclgvKbrdrJbgdNm,
|
||||
lclgvIdfrNm: submitInfo.lclgvIdfrNm,
|
||||
lclgvIdfrTelnoCn: submitInfo.lclgvIdfrTelnoCn,
|
||||
lclgvIdfrDeptNm: submitInfo.lclgvIdfrDeptNm,
|
||||
lclgvIdfrJbgdNm: submitInfo.lclgvIdfrJbgdNm,
|
||||
...(isPublicInstitutionTab ? { noApplicable } : {}),
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" as const } : {}),
|
||||
};
|
||||
|
||||
const verificationQuery = {
|
||||
@@ -1437,14 +1450,14 @@ const SewageExcretaRelatedInp = () => {
|
||||
tabKey === ExcretaRelatedIndustryInpColumn.tabKey
|
||||
? await statisticsInputSewageExcretaRelatedApi.submitExcretaRelatedIndustry({
|
||||
...submitRequestBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: submitList as ExcretaRelatedIndustrySaveItem[],
|
||||
})
|
||||
: tabKey === PrivateSewageTreatRelatedIndustryInpColumn.tabKey
|
||||
? await statisticsInputSewageExcretaRelatedApi.submitPrivateSewageTreatRelatedIndustry(
|
||||
{
|
||||
...submitRequestBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList:
|
||||
submitList as PrivateSewageTreatRelatedIndustrySaveItem[],
|
||||
},
|
||||
@@ -1453,17 +1466,17 @@ const SewageExcretaRelatedInp = () => {
|
||||
? await statisticsInputSewageExcretaRelatedApi.submitPublicInstitutionSewerIndustry(
|
||||
{
|
||||
...submitRequestBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: submitList as PublicInstitutionSewerIndustrySaveItem[],
|
||||
deleteList,
|
||||
deleteList: deleteList as PublicInstitutionSewerIndustrySaveItem[],
|
||||
},
|
||||
)
|
||||
: await statisticsInputSewageExcretaRelatedApi.submitPublicInstitutionExcretaIndustry(
|
||||
{
|
||||
...submitRequestBase,
|
||||
userId: userInfo.userUnqId,
|
||||
submitList: submitList as PublicInstitutionSewerIndustrySaveItem[],
|
||||
deleteList,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: submitList as PublicInstitutionExcretaIndustrySaveItem[],
|
||||
deleteList: deleteList as PublicInstitutionExcretaIndustrySaveItem[],
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1484,7 +1497,7 @@ const SewageExcretaRelatedInp = () => {
|
||||
stats_cd: statsCdForTab,
|
||||
stats_yr: lastSearchedValues.year ?? "",
|
||||
lae_sbmsn_stts_cd:
|
||||
activeTabStatus?.laeSbmsnSttsCd === "4" ? "4" : "2",
|
||||
activeTabStatus?.lclgvSbmsnSttsCd === "4" ? "4" : "2",
|
||||
gen_verify_stts_cd: nvpassed ? "1" : "2",
|
||||
hb_pass_yn: response.data?.isHbPassed,
|
||||
};
|
||||
@@ -1577,7 +1590,7 @@ const SewageExcretaRelatedInp = () => {
|
||||
setIsLoading(true);
|
||||
const userInfo = await getUserInfo();
|
||||
const userFields = {
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" as const } : {}),
|
||||
};
|
||||
|
||||
@@ -1628,7 +1641,7 @@ const SewageExcretaRelatedInp = () => {
|
||||
sggList = list;
|
||||
|
||||
const rowsForSave = mergeGridRowsWithMapRows(currentData, mapRows, [
|
||||
"ognzSn",
|
||||
PUBLIC_INSTITUTION_OGNZ_SN_FIELD,
|
||||
"admdstCd",
|
||||
"admdstNm",
|
||||
"__isNewArticle",
|
||||
@@ -1661,16 +1674,16 @@ const SewageExcretaRelatedInp = () => {
|
||||
{
|
||||
...baseSaveRequest,
|
||||
...userFields,
|
||||
saveList,
|
||||
deleteList,
|
||||
saveList: saveList as PublicInstitutionSewerIndustrySaveItem[],
|
||||
deleteList: deleteList as PublicInstitutionSewerIndustrySaveItem[],
|
||||
},
|
||||
)
|
||||
: await statisticsInputSewageExcretaRelatedApi.savePublicInstitutionExcretaIndustry(
|
||||
{
|
||||
...baseSaveRequest,
|
||||
...userFields,
|
||||
saveList,
|
||||
deleteList,
|
||||
saveList: saveList as PublicInstitutionExcretaIndustrySaveItem[],
|
||||
deleteList: deleteList as PublicInstitutionExcretaIndustrySaveItem[],
|
||||
},
|
||||
);
|
||||
} else {
|
||||
@@ -2011,7 +2024,7 @@ const SewageExcretaRelatedInp = () => {
|
||||
{submitOk && <SubmitButton submitFunc={submitFunc} />}
|
||||
</>
|
||||
)}
|
||||
{activeTabStatus?.laeSbmsnSttsCd === "2" && submitOk && (
|
||||
{activeTabStatus?.lclgvSbmsnSttsCd === "2" && submitOk && (
|
||||
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
|
||||
)}
|
||||
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
|
||||
|
||||
@@ -157,7 +157,7 @@ function verificationCellTooltip({
|
||||
? "일반검증 - "
|
||||
: "모집단 검증 - ";
|
||||
return `${prefix}${e.rsnExpln}
|
||||
(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`;
|
||||
(검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
@@ -343,8 +343,8 @@ const computeTabEnterable = (
|
||||
statusInfo: SewerageStatusInfo | null | undefined,
|
||||
periodEnterable: boolean,
|
||||
): boolean => {
|
||||
if (statusInfo?.laeSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.laeSbmsnSttsCd)) {
|
||||
if (statusInfo?.lclgvSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.lclgvSbmsnSttsCd)) {
|
||||
return false;
|
||||
}
|
||||
return periodEnterable;
|
||||
@@ -380,7 +380,7 @@ function computeTabToolbarSnapshot(
|
||||
): TabToolbarSnapshot {
|
||||
const gugunOk = buildGugunOkForToolbar(searchContext);
|
||||
const enterable = computeTabEnterable(statusInfo, periodEnterable);
|
||||
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2";
|
||||
const isSubmitted = statusInfo?.lclgvSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
|
||||
const showEnterableToolbarButtons = enterable && gugunOk;
|
||||
const showSubmittedInInputPeriodToolbar =
|
||||
@@ -504,39 +504,39 @@ function buildSlurgeProcessingFacStatusSaveItem(
|
||||
otsdInflwQnt: toGridNumber(row.otsdInflwQnt),
|
||||
transfTmplntNm: nullableTrimmedString(row.transfTmplntNm),
|
||||
transfPrcsQnt: toGridNumber(row.transfPrcsQnt),
|
||||
srpFncRt: toGridNumber(row.srpFncRt),
|
||||
itslfTs1RclmQnt: toGridNumber(row.itslfTs1RclmQnt),
|
||||
itslfTs1IncnrQnt: toGridNumber(row.itslfTs1IncnrQnt),
|
||||
itslfTs1AltdQnt: toGridNumber(row.itslfTs1AltdQnt),
|
||||
itslfTs1DryQnt: toGridNumber(row.itslfTs1DryQnt),
|
||||
itslfTs1FuelQnt: toGridNumber(row.itslfTs1FuelQnt),
|
||||
itslfTs1CmpstgQnt: toGridNumber(row.itslfTs1CmpstgQnt),
|
||||
sludgeFncRt: toGridNumber(row.sludgeFncRt),
|
||||
itslfFrlvlRclmQnt: toGridNumber(row.itslfFrlvlRclmQnt),
|
||||
itslfFrlvlIncnrQnt: toGridNumber(row.itslfFrlvlIncnrQnt),
|
||||
itslfFrlvlSldQnt: toGridNumber(row.itslfFrlvlSldQnt),
|
||||
itslfFrlvlDryQnt: toGridNumber(row.itslfFrlvlDryQnt),
|
||||
itslfFrlvlCrbnQnt: toGridNumber(row.itslfFrlvlCrbnQnt),
|
||||
itslfFrlvlCmpstgQnt: toGridNumber(row.itslfFrlvlCmpstgQnt),
|
||||
cmpsPrcsFcltBzentyNm: nullableTrimmedString(row.cmpsPrcsFcltBzentyNm),
|
||||
incnrPrcsLfvlm: toGridNumber(row.incnrPrcsLfvlm),
|
||||
incnrPrcsPrdctRmtrlQnt: toGridNumber(row.incnrPrcsPrdctRmtrlQnt),
|
||||
incnrPrcsFmlyrQnt: toGridNumber(row.incnrPrcsFmlyrQnt),
|
||||
incnrPrcsCwthQnt: toGridNumber(row.incnrPrcsCwthQnt),
|
||||
incnrPrcsFcltNm: nullableTrimmedString(row.incnrPrcsFcltNm),
|
||||
stbznPrcsLfvlm: toGridNumber(row.stbznPrcsLfvlm),
|
||||
stbznPrcsFmlyrQnt: toGridNumber(row.stbznPrcsFmlyrQnt),
|
||||
stbznPrcsFcltNm: nullableTrimmedString(row.stbznPrcsFcltNm),
|
||||
sldPrcsLfvlm: toGridNumber(row.sldPrcsLfvlm),
|
||||
sldPrcsCwthQnt: toGridNumber(row.sldPrcsCwthQnt),
|
||||
sldPrcsFcltNm: nullableTrimmedString(row.sldPrcsFcltNm),
|
||||
dryPrcsLfvlm: toGridNumber(row.dryPrcsLfvlm),
|
||||
dryPrcsIncnrQnt: toGridNumber(row.dryPrcsIncnrQnt),
|
||||
dryPrcsFuelQnt: toGridNumber(row.dryPrcsFuelQnt),
|
||||
dryPrcsPrdctRmtrlQnt: toGridNumber(row.dryPrcsPrdctRmtrlQnt),
|
||||
dryPrcsFmlyrQnt: toGridNumber(row.dryPrcsFmlyrQnt),
|
||||
dryPrcsCwthQnt: toGridNumber(row.dryPrcsCwthQnt),
|
||||
dryPrcsCmpstgQnt: toGridNumber(row.dryPrcsCmpstgQnt),
|
||||
dryPrcsFcltNm: nullableTrimmedString(row.dryPrcsFcltNm),
|
||||
fuelPrcsIncnrQnt: toGridNumber(row.fuelPrcsIncnrQnt),
|
||||
fuelPrcsFuelQnt: toGridNumber(row.fuelPrcsFuelQnt),
|
||||
fuelPrcsFcltNm: nullableTrimmedString(row.fuelPrcsFcltNm),
|
||||
crbnPrcsIncnrQnt: toGridNumber(row.crbnPrcsIncnrQnt),
|
||||
crbnPrcsFuelQnt: toGridNumber(row.crbnPrcsFuelQnt),
|
||||
crbnPrcsFcltNm: nullableTrimmedString(row.crbnPrcsFcltNm),
|
||||
otsdCnsgnRclmQnt: toGridNumber(row.otsdCnsgnRclmQnt),
|
||||
otsdCnsgnIncnrQnt: toGridNumber(row.otsdCnsgnIncnrQnt),
|
||||
otsdCnsgnFuelPwplQnt: toGridNumber(row.otsdCnsgnFuelPwplQnt),
|
||||
otsdCnsgnPwplQnt: toGridNumber(row.otsdCnsgnPwplQnt),
|
||||
otsdCnsgnPrdctRmtrlQnt: toGridNumber(row.otsdCnsgnPrdctRmtrlQnt),
|
||||
otsdCnsgnFmlyrQnt: toGridNumber(row.otsdCnsgnFmlyrQnt),
|
||||
otsdCnsgnCwthQnt: toGridNumber(row.otsdCnsgnCwthQnt),
|
||||
otsdCnsgnCmpstgQnt: toGridNumber(row.otsdCnsgnCmpstgQnt),
|
||||
otsdCnsgnFcltNm: nullableTrimmedString(row.otsdCnsgnFcltNm),
|
||||
cparTs1Whol: toGridNumber(row.cparTs1Whol),
|
||||
cparFrlvlWholQnt: toGridNumber(row.cparFrlvlWholQnt),
|
||||
rmrk: nullableTrimmedString(row.rmrk),
|
||||
};
|
||||
}
|
||||
@@ -553,23 +553,23 @@ function buildDigestorOperatingStatusSaveItem(
|
||||
swtrmtFcltCd: nullableTrimmedString(row.swtrmtFcltCd),
|
||||
swtrmtFcltNm: nullableTrimmedString(row.swtrmtFcltNm),
|
||||
fcltSn: nullableTrimmedString(row.fcltSn),
|
||||
digstnEnYn: nullableTrimmedString(row.digstnEnYn),
|
||||
gasPrcsEnYn: nullableTrimmedString(row.gasPrcsEnYn),
|
||||
};
|
||||
|
||||
if (isDigestorOperatingRowDisabled(row)) {
|
||||
return {
|
||||
...base,
|
||||
digstnEnYn: "N",
|
||||
digstnCpct: 0,
|
||||
gasPrcsEnYn: "N",
|
||||
gasPrcsCpct: 0,
|
||||
actlDrvnCpct: 0,
|
||||
digstnGasInputQnt: 0,
|
||||
digstnGasInputSldDnst: 0,
|
||||
digstnTnkOprtngDayCnt: 0,
|
||||
prcsGasInputQnt: 0,
|
||||
prcsGasInputSldDnst: 0,
|
||||
prcsRepoOprtngDayCnt: 0,
|
||||
ocrnGasQnt: 0,
|
||||
digstnGasUtztnNtslQnt: 0,
|
||||
digstnGasUtztnEtgtQnt: 0,
|
||||
digstnGasUtztnItslfQnt: 0,
|
||||
digstnGasUtztnEtcQnt: 0,
|
||||
prcsGasUtztnNtslQnt: 0,
|
||||
prcsGasUtztnEtgtQnt: 0,
|
||||
prcsGasUtztnItslfQnt: 0,
|
||||
prcsGasUtztnEtcQnt: 0,
|
||||
surplsGasQnt: 0,
|
||||
rmrk: null,
|
||||
};
|
||||
@@ -577,16 +577,16 @@ function buildDigestorOperatingStatusSaveItem(
|
||||
|
||||
return {
|
||||
...base,
|
||||
digstnCpct: toGridNumber(row.digstnCpct),
|
||||
gasPrcsCpct: toGridNumber(row.gasPrcsCpct),
|
||||
actlDrvnCpct: toGridNumber(row.actlDrvnCpct),
|
||||
digstnGasInputQnt: toGridNumber(row.digstnGasInputQnt),
|
||||
digstnGasInputSldDnst: toGridNumber(row.digstnGasInputSldDnst),
|
||||
digstnTnkOprtngDayCnt: toGridNumber(row.digstnTnkOprtngDayCnt),
|
||||
prcsGasInputQnt: toGridNumber(row.prcsGasInputQnt),
|
||||
prcsGasInputSldDnst: toGridNumber(row.prcsGasInputSldDnst),
|
||||
prcsRepoOprtngDayCnt: toGridNumber(row.prcsRepoOprtngDayCnt),
|
||||
ocrnGasQnt: toGridNumber(row.ocrnGasQnt),
|
||||
digstnGasUtztnNtslQnt: toGridNumber(row.digstnGasUtztnNtslQnt),
|
||||
digstnGasUtztnEtgtQnt: toGridNumber(row.digstnGasUtztnEtgtQnt),
|
||||
digstnGasUtztnItslfQnt: toGridNumber(row.digstnGasUtztnItslfQnt),
|
||||
digstnGasUtztnEtcQnt: toGridNumber(row.digstnGasUtztnEtcQnt),
|
||||
prcsGasUtztnNtslQnt: toGridNumber(row.prcsGasUtztnNtslQnt),
|
||||
prcsGasUtztnEtgtQnt: toGridNumber(row.prcsGasUtztnEtgtQnt),
|
||||
prcsGasUtztnItslfQnt: toGridNumber(row.prcsGasUtztnItslfQnt),
|
||||
prcsGasUtztnEtcQnt: toGridNumber(row.prcsGasUtztnEtcQnt),
|
||||
surplsGasQnt: toGridNumber(row.surplsGasQnt),
|
||||
rmrk: nullableTrimmedString(row.rmrk),
|
||||
};
|
||||
@@ -604,10 +604,10 @@ function buildSewageSlurgeProcessingFacStatusSaveItem(
|
||||
fcltSn: nullableTrimmedString(row.fcltSn),
|
||||
fcltNm: nullableTrimmedString(row.fcltNm),
|
||||
prcsMthExpln: nullableTrimmedString(row.prcsMthExpln),
|
||||
fcltCpct: toGridNumber(row.fcltCpct),
|
||||
sludgeFcltCpct: toGridNumber(row.sludgeFcltCpct),
|
||||
anlPrcsQnt: toGridNumber(row.anlPrcsQnt),
|
||||
dayAvgOprtngHr: toGridNumber(row.dayAvgOprtngHr),
|
||||
anlOprtngDayCnt: toGridNumber(row.anlOprtngDayCnt),
|
||||
dyrgOprtngHr: toGridNumber(row.dyrgOprtngHr),
|
||||
fcltAnlOprtngDayCnt: toGridNumber(row.fcltAnlOprtngDayCnt),
|
||||
rmrk: nullableTrimmedString(row.rmrk),
|
||||
};
|
||||
}
|
||||
@@ -630,7 +630,7 @@ function buildExcretaSlurgeProcessingStatusSaveItem(
|
||||
oceanInputQnt: toGridNumber(row.oceanInputQnt),
|
||||
fuelQnt: toGridNumber(row.fuelQnt),
|
||||
etcQnt: toGridNumber(row.etcQnt),
|
||||
nondspsQnt: toGridNumber(row.nondspsQnt),
|
||||
unprcsQnt: toGridNumber(row.unprcsQnt),
|
||||
swtrmtFcltNm: nullableTrimmedString(row.swtrmtFcltNm),
|
||||
rmrk: nullableTrimmedString(row.rmrk),
|
||||
};
|
||||
@@ -1357,11 +1357,6 @@ const SewageExcretaSlurgeInp = () => {
|
||||
: rowsForSubmit.map((row) =>
|
||||
buildExcretaSlurgeProcessingStatusSaveItem(row, list),
|
||||
);
|
||||
const slurgeDeleteList = isSlurgeFacTab
|
||||
? (deleteListByTab.get(tabKey) ?? [])
|
||||
.filter((row) => isPersistedArticleRowForTab(tabKey, row))
|
||||
.map((row) => buildSlurgeProcessingFacStatusSaveItem(row, list))
|
||||
: [];
|
||||
const sewageSlurgeFacDeleteList = isSewageSlurgeFacTab
|
||||
? (deleteListByTab.get(tabKey) ?? [])
|
||||
.filter((row) => isPersistedArticleRowForTab(tabKey, row))
|
||||
@@ -1423,16 +1418,15 @@ const SewageExcretaSlurgeInp = () => {
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
userId: "",
|
||||
laePicNm: submitInfo.laePicNm,
|
||||
laePicTelNo: submitInfo.laePicTelNo,
|
||||
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm,
|
||||
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm,
|
||||
laeIdfrNm: submitInfo.laeIdfrNm,
|
||||
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
|
||||
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
|
||||
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" as const } : {}),
|
||||
userUuid: "",
|
||||
lclgvPicNm: submitInfo.lclgvPicNm,
|
||||
lclgvPicTelnoCn: submitInfo.lclgvPicTelnoCn,
|
||||
lclgvKbrdrDeptNm: submitInfo.lclgvKbrdrDeptNm,
|
||||
lclgvKbrdrJbgdNm: submitInfo.lclgvKbrdrJbgdNm,
|
||||
lclgvIdfrNm: submitInfo.lclgvIdfrNm,
|
||||
lclgvIdfrTelnoCn: submitInfo.lclgvIdfrTelnoCn,
|
||||
lclgvIdfrDeptNm: submitInfo.lclgvIdfrDeptNm,
|
||||
lclgvIdfrJbgdNm: submitInfo.lclgvIdfrJbgdNm,
|
||||
noApplicable,
|
||||
};
|
||||
|
||||
@@ -1443,21 +1437,20 @@ const SewageExcretaSlurgeInp = () => {
|
||||
const response = isSlurgeFacTab
|
||||
? await statisticsInputSewageExcretaSlurgeApi.submitSlurgeProcessingFacStatus({
|
||||
...submitRequestBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: slurgeSubmitList,
|
||||
deleteList: slurgeDeleteList,
|
||||
})
|
||||
: isDigestorTab
|
||||
? await statisticsInputSewageExcretaSlurgeApi.submitDigestorOperatingStatus({
|
||||
...submitRequestBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: digestorSubmitList,
|
||||
})
|
||||
: isSewageSlurgeFacTab
|
||||
? await statisticsInputSewageExcretaSlurgeApi.submitSewageSlurgeProcessingFacStatus(
|
||||
{
|
||||
...submitRequestBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: sewageSlurgeFacSubmitList,
|
||||
deleteList: sewageSlurgeFacDeleteList,
|
||||
},
|
||||
@@ -1465,7 +1458,7 @@ const SewageExcretaSlurgeInp = () => {
|
||||
: await statisticsInputSewageExcretaSlurgeApi.submitExcretaSlurgeProcessingStatus(
|
||||
{
|
||||
...submitRequestBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: excretaSlurgeSubmitList,
|
||||
},
|
||||
);
|
||||
@@ -1487,7 +1480,7 @@ const SewageExcretaSlurgeInp = () => {
|
||||
stats_cd: statsCdForSubmit,
|
||||
stats_yr: lastSearchedValues.year ?? "",
|
||||
lae_sbmsn_stts_cd:
|
||||
activeTabStatus?.laeSbmsnSttsCd === "4" ? "4" : "2",
|
||||
activeTabStatus?.lclgvSbmsnSttsCd === "4" ? "4" : "2",
|
||||
gen_verify_stts_cd: nvpassed ? "1" : "2",
|
||||
hb_pass_yn: response.data?.isHbPassed,
|
||||
};
|
||||
@@ -1564,7 +1557,7 @@ const SewageExcretaSlurgeInp = () => {
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
mdfrId: userInfo.userUnqId,
|
||||
mdfrUuid: userInfo.userUnqId,
|
||||
statsCd: statsCdForTab,
|
||||
};
|
||||
|
||||
@@ -1636,7 +1629,7 @@ const SewageExcretaSlurgeInp = () => {
|
||||
setIsLoading(true);
|
||||
const userInfo = await getUserInfo();
|
||||
const userFields = {
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" as const } : {}),
|
||||
};
|
||||
|
||||
@@ -1655,9 +1648,6 @@ const SewageExcretaSlurgeInp = () => {
|
||||
: rowsForSave.map((row) =>
|
||||
buildSlurgeProcessingFacStatusSaveItem(row, list),
|
||||
);
|
||||
const deleteList = (deleteListByTab.get(tabKey) ?? [])
|
||||
.filter((row) => isPersistedArticleRowForTab(tabKey, row))
|
||||
.map((row) => buildSlurgeProcessingFacStatusSaveItem(row, list));
|
||||
|
||||
if (!noApplicable && saveList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) {
|
||||
alert("해당사항 없음을 체크해주세요.");
|
||||
@@ -1670,7 +1660,6 @@ const SewageExcretaSlurgeInp = () => {
|
||||
...baseSaveRequest,
|
||||
...userFields,
|
||||
saveList,
|
||||
deleteList,
|
||||
},
|
||||
);
|
||||
} else if (tabKey === DigestorOperatingStatusInpColumn.tabKey) {
|
||||
@@ -2109,7 +2098,7 @@ const SewageExcretaSlurgeInp = () => {
|
||||
{submitOk && <SubmitButton submitFunc={submitFunc} />}
|
||||
</>
|
||||
)}
|
||||
{activeTabStatus?.laeSbmsnSttsCd === "2" && submitOk && (
|
||||
{activeTabStatus?.lclgvSbmsnSttsCd === "2" && submitOk && (
|
||||
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
|
||||
)}
|
||||
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
|
||||
|
||||
@@ -81,7 +81,10 @@ import {
|
||||
patchVerificationFailedByTab,
|
||||
} from "@/components/util/input/verification-failed-flag";
|
||||
import { nullableTrimmedString } from "@/components/util/input/nullableString";
|
||||
import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display";
|
||||
import {
|
||||
getVerificationErrorsForField,
|
||||
type VerificationResultLikeMap,
|
||||
} from "@/components/util/input/verification-result-display";
|
||||
import { useAdminStore } from "@/store/admin-store";
|
||||
import { useStatisticsInputFocusStore } from "@/store/statistics-input-focus-store";
|
||||
import { useTabStore } from "@/store/tab-store";
|
||||
@@ -91,8 +94,8 @@ const computeTabEnterable = (
|
||||
statusInfo: SewerageStatusInfo | null | undefined,
|
||||
periodEnterable: boolean,
|
||||
): boolean => {
|
||||
if (statusInfo?.laeSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.laeSbmsnSttsCd)) {
|
||||
if (statusInfo?.lclgvSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.lclgvSbmsnSttsCd)) {
|
||||
return false;
|
||||
}
|
||||
return periodEnterable;
|
||||
@@ -219,18 +222,33 @@ const applyPipeTypeStatusEditableColumns = (
|
||||
});
|
||||
};
|
||||
|
||||
/** 2-3 관종별: verificationResult.prlgWhol → 총계 계(pipeByPrstWhol) */
|
||||
const PIPE_TYPE_TOTAL_WHOL_ALIASES = ["pipeWholPrlg", "prlgWhol"] as const;
|
||||
|
||||
/** 2-3 관종별: 총계 검증 키(pipeWholPrlg/prlgWhol) → 총계 계(pipeByPrstWhol) */
|
||||
function remapPipeTypePrlgWholVerification(
|
||||
result?: SeweragePipeInputVerificationResult | null,
|
||||
): SeweragePipeInputVerificationResult | null | undefined {
|
||||
if (!result) return result;
|
||||
const fromAlias = result.prlgWhol;
|
||||
if (!Array.isArray(fromAlias) || fromAlias.length === 0) return result;
|
||||
const existing = result.pipeByPrstWhol;
|
||||
if (Array.isArray(existing) && existing.length > 0) return result;
|
||||
const fromAlias = result.pipeWholPrlg ?? result.prlgWhol;
|
||||
if (!Array.isArray(fromAlias) || fromAlias.length === 0) return result;
|
||||
return { ...result, pipeByPrstWhol: fromAlias };
|
||||
}
|
||||
|
||||
function getSewagePipeRowVerificationErrors(
|
||||
verification: SeweragePipeInputVerificationResult | undefined,
|
||||
fieldName: string,
|
||||
tabKey: string,
|
||||
) {
|
||||
if (!verification) return undefined;
|
||||
const aliases =
|
||||
tabKey === PIPE_TYPE_STATUS_TAB_KEY && fieldName === "pipeByPrstWhol"
|
||||
? PIPE_TYPE_TOTAL_WHOL_ALIASES
|
||||
: undefined;
|
||||
return getVerificationErrorsForField(verification, fieldName, aliases);
|
||||
}
|
||||
|
||||
/** 관종별 신 응답(yearlyStatList) -> 그리드 y{year}* 필드로 정규화 */
|
||||
function normalizePipeTypeStatusRow(
|
||||
row: SeweragePipeInputPipeTypeStatusItem,
|
||||
@@ -242,6 +260,7 @@ function normalizePipeTypeStatusRow(
|
||||
lyngPrlgWhol,
|
||||
dmltnPrlgWhol,
|
||||
verificationResult,
|
||||
rmrk,
|
||||
...rest
|
||||
} = row;
|
||||
const yearFields: Record<string, number> = {};
|
||||
@@ -256,7 +275,7 @@ function normalizePipeTypeStatusRow(
|
||||
if (!Number.isFinite(year)) continue;
|
||||
const targetYear = Math.min(lastDataYear, Math.max(1995, year));
|
||||
yearFields[`y${targetYear}PrlgWhol`] =
|
||||
(yearFields[`y${targetYear}PrlgWhol`] ?? 0) + Number(y.prlgWhol ?? 0);
|
||||
(yearFields[`y${targetYear}PrlgWhol`] ?? 0) + Number(y.pipeWholPrlg ?? 0);
|
||||
yearFields[`y${targetYear}LyngPrlg`] =
|
||||
(yearFields[`y${targetYear}LyngPrlg`] ?? 0) + Number(y.lyngPrlg ?? 0);
|
||||
yearFields[`y${targetYear}DmltnPrlg`] =
|
||||
@@ -266,8 +285,9 @@ function normalizePipeTypeStatusRow(
|
||||
return {
|
||||
ctpvNm,
|
||||
...rest,
|
||||
lyngPrlg: Number(row.lyngPrlg ?? lyngPrlgWhol ?? 0),
|
||||
dmltnPrlg: Number(row.dmltnPrlg ?? dmltnPrlgWhol ?? 0),
|
||||
rmrk: rmrk ?? "",
|
||||
lyngPrlg: Number(lyngPrlgWhol ?? 0),
|
||||
dmltnPrlg: Number(dmltnPrlgWhol ?? 0),
|
||||
...yearFields,
|
||||
verificationResult: remapPipeTypePrlgWholVerification(verificationResult) ?? undefined,
|
||||
};
|
||||
@@ -330,7 +350,6 @@ function buildRegionalFactSubmitListFromGrid(
|
||||
};
|
||||
return currentData.map((d) => ({
|
||||
admdstCd: nullableTrimmedString(d.admdstCd),
|
||||
sggNm: nullableTrimmedString(d.sggNm),
|
||||
admdstNm: nullableTrimmedString(d.admdstNm),
|
||||
goalYr: nullableTrimmedString(d.goalYr),
|
||||
wholPplMtncPlanPrlg: formatNumber(d.wholPplMtncPlanPrlg, "wholPplMtncPlanPrlg"),
|
||||
@@ -341,29 +360,29 @@ function buildRegionalFactSubmitListFromGrid(
|
||||
cbsysPplMtncPlanPrlg: formatNumber(d.cbsysPplMtncPlanPrlg, "cbsysPplMtncPlanPrlg"),
|
||||
cbsysPlanPrlg: formatNumber(d.cbsysPlanPrlg, "cbsysPlanPrlg"),
|
||||
cbsysFcltPrlg: formatNumber(d.cbsysFcltPrlg, "cbsysFcltPrlg"),
|
||||
cbsysSqrePrlg: formatNumber(d.cbsysSqrePrlg, "cbsysSqrePrlg"),
|
||||
cbsysCiclPrlg: formatNumber(d.cbsysCiclPrlg, "cbsysCiclPrlg"),
|
||||
cbsysShpPrlg: formatNumber(d.cbsysShpPrlg, "cbsysShpPrlg"),
|
||||
cbsysElpsdPrlg: formatNumber(d.cbsysElpsdPrlg, "cbsysElpsdPrlg"),
|
||||
cbsysOpnPrlg: formatNumber(d.cbsysOpnPrlg, "cbsysOpnPrlg"),
|
||||
cbsysGttrPrlg: formatNumber(d.cbsysGttrPrlg, "cbsysGttrPrlg"),
|
||||
cbsysLaditPrlg: formatNumber(d.cbsysLaditPrlg, "cbsysLaditPrlg"),
|
||||
drtwPplMtncPlanArea: formatNumber(d.drtwPplMtncPlanArea, "drtwPplMtncPlanArea"),
|
||||
drtwPplMtncPlanPrlg: formatNumber(d.drtwPplMtncPlanPrlg, "drtwPplMtncPlanPrlg"),
|
||||
drtwPplPlanPrlg: formatNumber(d.drtwPplPlanPrlg, "drtwPplPlanPrlg"),
|
||||
drtwPplFcltPrlg: formatNumber(d.drtwPplFcltPrlg, "drtwPplFcltPrlg"),
|
||||
drtwPplSqrePrlg: formatNumber(d.drtwPplSqrePrlg, "drtwPplSqrePrlg"),
|
||||
drtwPplCiclPrlg: formatNumber(d.drtwPplCiclPrlg, "drtwPplCiclPrlg"),
|
||||
drtwPplShpPrlg: formatNumber(d.drtwPplShpPrlg, "drtwPplShpPrlg"),
|
||||
drtwPplElpsdPrlg: formatNumber(d.drtwPplElpsdPrlg, "drtwPplElpsdPrlg"),
|
||||
exlnPplMtncPlanArea: formatNumber(d.exlnPplMtncPlanArea, "exlnPplMtncPlanArea"),
|
||||
exlnPplMtncPlanPrlg: formatNumber(d.exlnPplMtncPlanPrlg, "exlnPplMtncPlanPrlg"),
|
||||
exlnPplPlanPrlg: formatNumber(d.exlnPplPlanPrlg, "exlnPplPlanPrlg"),
|
||||
exlnPplFcltPrlg: formatNumber(d.exlnPplFcltPrlg, "exlnPplFcltPrlg"),
|
||||
exlnPplSqrePrlg: formatNumber(d.exlnPplSqrePrlg, "exlnPplSqrePrlg"),
|
||||
exlnPplCiclPrlg: formatNumber(d.exlnPplCiclPrlg, "exlnPplCiclPrlg"),
|
||||
exlnPplShpPrlg: formatNumber(d.exlnPplShpPrlg, "exlnPplShpPrlg"),
|
||||
exlnPplElpsdPrlg: formatNumber(d.exlnPplElpsdPrlg, "exlnPplElpsdPrlg"),
|
||||
exlnPplOpnPrlg: formatNumber(d.exlnPplOpnPrlg, "exlnPplOpnPrlg"),
|
||||
exlnPplGttrPrlg: formatNumber(d.exlnPplGttrPrlg, "exlnPplGttrPrlg"),
|
||||
exlnPplLaditPrlg: formatNumber(d.exlnPplLaditPrlg, "exlnPplLaditPrlg"),
|
||||
cbsysMnhlCnt: formatNumber(d.cbsysMnhlCnt, "cbsysMnhlCnt"),
|
||||
sprsysDrtwMnhlCnt: formatNumber(d.sprsysDrtwMnhlCnt, "sprsysDrtwMnhlCnt"),
|
||||
sprsysExlnMnhlCnt: formatNumber(d.sprsysExlnMnhlCnt, "sprsysExlnMnhlCnt"),
|
||||
drtwRcvprpCnt: formatNumber(d.drtwRcvprpCnt, "drtwRcvprpCnt"),
|
||||
exlnRcvprpCnt: formatNumber(d.exlnRcvprpCnt, "exlnRcvprpCnt"),
|
||||
drtwDrgCnt: formatNumber(d.drtwDrgCnt, "drtwDrgCnt"),
|
||||
exlnDrgCnt: formatNumber(d.exlnDrgCnt, "exlnDrgCnt"),
|
||||
stofcbrCnt: formatNumber(d.stofcbrCnt, "stofcbrCnt"),
|
||||
exlnOtflCnt: formatNumber(d.exlnOtflCnt, "exlnOtflCnt"),
|
||||
sewerLdgrEdpPrlg: formatNumber(d.sewerLdgrEdpPrlg, "sewerLdgrEdpPrlg"),
|
||||
@@ -419,7 +438,7 @@ function computeTabToolbarSnapshot(
|
||||
): TabToolbarSnapshot {
|
||||
const gugunOk = buildGugunOkForToolbar(searchContext);
|
||||
const enterable = computeTabEnterable(statusInfo, periodEnterable);
|
||||
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2";
|
||||
const isSubmitted = statusInfo?.lclgvSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
|
||||
const showEnterableToolbarButtons = enterable && gugunOk;
|
||||
const showSubmittedInInputPeriodToolbar = isSubmitted && periodEnterable && gugunOk;
|
||||
@@ -1109,7 +1128,7 @@ const SewagePipeInp = () => {
|
||||
...toInputSearchRegion(sidoListRef.current, searchValues),
|
||||
ctpvNm: searchValues?.sido ?? "",
|
||||
statsYr: searchValues?.year ?? "",
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
statsCd: statsCdForTab,
|
||||
};
|
||||
|
||||
@@ -1191,15 +1210,15 @@ const SewagePipeInp = () => {
|
||||
statsYr: values.year ?? "",
|
||||
ctpvNm: values.sido ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, values),
|
||||
userId: userInfo.userUnqId,
|
||||
laePicNm: submitInfo.laePicNm,
|
||||
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm,
|
||||
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm,
|
||||
laePicTelNo: submitInfo.laePicTelNo,
|
||||
laeIdfrNm: submitInfo.laeIdfrNm,
|
||||
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
|
||||
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
|
||||
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
|
||||
userUuid: userInfo.userUnqId,
|
||||
lclgvPicNm: submitInfo.lclgvPicNm,
|
||||
lclgvKbrdrJbgdNm: submitInfo.lclgvKbrdrJbgdNm,
|
||||
lclgvKbrdrDeptNm: submitInfo.lclgvKbrdrDeptNm,
|
||||
lclgvPicTelnoCn: submitInfo.lclgvPicTelnoCn,
|
||||
lclgvIdfrNm: submitInfo.lclgvIdfrNm,
|
||||
lclgvIdfrJbgdNm: submitInfo.lclgvIdfrJbgdNm,
|
||||
lclgvIdfrDeptNm: submitInfo.lclgvIdfrDeptNm,
|
||||
lclgvIdfrTelnoCn: submitInfo.lclgvIdfrTelnoCn,
|
||||
};
|
||||
let response: SeweragePipeInputSubmitResponse | null = null;
|
||||
|
||||
@@ -1244,20 +1263,19 @@ const SewagePipeInp = () => {
|
||||
};
|
||||
const submitList = currentData.map((d) => ({
|
||||
admdstCd: nullableTrimmedString(d.admdstCd),
|
||||
sggNm: nullableTrimmedString(d.sggNm),
|
||||
admdstNm: nullableTrimmedString(d.admdstNm),
|
||||
fcltSn: nullableTrimmedString(d.fcltSn),
|
||||
swtrmtFcltCd: nullableTrimmedString(d.swtrmtFcltCd),
|
||||
swtrmtFcltNm: nullableTrimmedString(d.swtrmtFcltNm),
|
||||
prcsZoneArea: formatNumber(d.prcsZoneArea, "prcsZoneArea"),
|
||||
cbsysPrcsZoneArea: formatNumber(d.cbsysPrcsZoneArea, "cbsysPrcsZoneArea"),
|
||||
sprsysPrcsZoneArea: formatNumber(d.sprsysPrcsZoneArea, "sprsysPrcsZoneArea"),
|
||||
cbsysTrtznArea: formatNumber(d.cbsysTrtznArea, "cbsysTrtznArea"),
|
||||
sprsysTrtznArea: formatNumber(d.sprsysTrtznArea, "sprsysTrtznArea"),
|
||||
sprsysVartnRt: formatNumber(d.sprsysVartnRt, "sprsysVartnRt"),
|
||||
mltvEqpmCnt: formatNumber(d.mltvEqpmCnt, "mltvEqpmCnt"),
|
||||
cbsysMltvEqpmCnt: formatNumber(d.cbsysMltvEqpmCnt, "cbsysMltvEqpmCnt"),
|
||||
sprsysMltvEqpmCnt: formatNumber(d.sprsysMltvEqpmCnt, "sprsysMltvEqpmCnt"),
|
||||
sprsysMltvLnkgCnt: formatNumber(d.sprsysMltvLnkgCnt, "sprsysMltvLnkgCnt"),
|
||||
sprsysMltvYetLnkgCnt: formatNumber(d.sprsysMltvYetLnkgCnt, "sprsysMltvYetLnkgCnt"),
|
||||
wholDrgCnt: formatNumber(d.wholDrgCnt, "wholDrgCnt"),
|
||||
cbsysDrgCnt: formatNumber(d.cbsysDrgCnt, "cbsysDrgCnt"),
|
||||
sprsysDrgCnt: formatNumber(d.sprsysDrgCnt, "sprsysDrgCnt"),
|
||||
sprsysDrgLnkgCnt: formatNumber(d.sprsysDrgLnkgCnt, "sprsysDrgLnkgCnt"),
|
||||
sprsysDrgNoinsLnkgCnt: formatNumber(d.sprsysDrgNoinsLnkgCnt, "sprsysDrgNoinsLnkgCnt"),
|
||||
wholFcltPrlg: formatNumber(d.wholFcltPrlg, "wholFcltPrlg"),
|
||||
wholIcptgAtlfcsPrlg: formatNumber(d.wholIcptgAtlfcsPrlg, "wholIcptgAtlfcsPrlg"),
|
||||
icptgPrlg: formatNumber(d.icptgPrlg, "icptgPrlg"),
|
||||
@@ -1265,10 +1283,10 @@ const SewagePipeInp = () => {
|
||||
cbsysStofcbrCnt: formatNumber(d.cbsysStofcbrCnt, "cbsysStofcbrCnt"),
|
||||
sprsysStofcbrCnt: formatNumber(d.sprsysStofcbrCnt, "sprsysStofcbrCnt"),
|
||||
sprsysExlnOtflCnt: formatNumber(d.sprsysExlnOtflCnt, "sprsysExlnOtflCnt"),
|
||||
wholBrchlnPrlg: formatNumber(d.wholBrchlnPrlg, "wholBrchlnPrlg"),
|
||||
cbsysBrchlnPrlg: formatNumber(d.cbsysBrchlnPrlg, "cbsysBrchlnPrlg"),
|
||||
exlnBrchlnPrlg: formatNumber(d.exlnBrchlnPrlg, "exlnBrchlnPrlg"),
|
||||
drtwBrchlnPrlg: formatNumber(d.drtwBrchlnPrlg, "drtwBrchlnPrlg"),
|
||||
wholPplPrlg: formatNumber(d.wholPplPrlg, "wholPplPrlg"),
|
||||
cbsysPplPrlg: formatNumber(d.cbsysPplPrlg, "cbsysPplPrlg"),
|
||||
exlnPplPrlg: formatNumber(d.exlnPplPrlg, "exlnPplPrlg"),
|
||||
drtwPplPrlg: formatNumber(d.drtwPplPrlg, "drtwPplPrlg"),
|
||||
rmrk: nullableTrimmedString(d.rmrk),
|
||||
}));
|
||||
const requestData: SeweragePipeInputFactInstallStatusSubmitRequest = {
|
||||
@@ -1309,7 +1327,7 @@ const SewagePipeInp = () => {
|
||||
lyngYr: String(year),
|
||||
lyngPrlg: formatNumber(d[lyngKey] as string | number | undefined, lyngKey),
|
||||
dmltnPrlg: formatNumber(d[dmltnKey] as string | number | undefined, dmltnKey),
|
||||
prlgWhol: formatNumber(d[wholKey] as string | number | undefined, wholKey),
|
||||
pipeWholPrlg: formatNumber(d[wholKey] as string | number | undefined, wholKey),
|
||||
});
|
||||
}
|
||||
return {
|
||||
@@ -1352,15 +1370,15 @@ const SewagePipeInp = () => {
|
||||
};
|
||||
const submitList = currentData.map((d) => ({
|
||||
pipeSeCd: nullableTrimmedString(d.pipeSeCd),
|
||||
step1PdmtPrlg: formatNumber(d.step1PdmtPrlg, "step1PdmtPrlg"),
|
||||
step2PdmtPrlg: formatNumber(d.step2PdmtPrlg, "step2PdmtPrlg"),
|
||||
step3PdmtPrlg: formatNumber(d.step3PdmtPrlg, "step3PdmtPrlg"),
|
||||
step4PdmtPrlg: formatNumber(d.step4PdmtPrlg, "step4PdmtPrlg"),
|
||||
step5PdmtPrlg: formatNumber(d.step5PdmtPrlg, "step5PdmtPrlg"),
|
||||
sqrePdmtPrlg: formatNumber(d.sqrePdmtPrlg, "sqrePdmtPrlg"),
|
||||
frlvlPdmtPrlg: formatNumber(d.frlvlPdmtPrlg, "frlvlPdmtPrlg"),
|
||||
sclvlPdmtPrlg: formatNumber(d.sclvlPdmtPrlg, "sclvlPdmtPrlg"),
|
||||
thlvlPdmtPrlg: formatNumber(d.thlvlPdmtPrlg, "thlvlPdmtPrlg"),
|
||||
folvlPdmtPrlg: formatNumber(d.folvlPdmtPrlg, "folvlPdmtPrlg"),
|
||||
filvlPdmtPrlg: formatNumber(d.filvlPdmtPrlg, "filvlPdmtPrlg"),
|
||||
shpPdmtPrlg: formatNumber(d.shpPdmtPrlg, "shpPdmtPrlg"),
|
||||
etcPdmtPrlg: formatNumber(d.etcPdmtPrlg, "etcPdmtPrlg"),
|
||||
rmrk: nullableTrimmedString(d.rmrk),
|
||||
pdmtByTsmWhol: formatNumber(d.pdmtByTsmWhol, "pdmtByTsmWhol"),
|
||||
pdmtSeWholPrlg: formatNumber(d.pdmtSeWholPrlg, "pdmtSeWholPrlg"),
|
||||
admdstCd: nullableTrimmedString(d.admdstCd),
|
||||
admdstNm: nullableTrimmedString(d.admdstNm),
|
||||
}));
|
||||
@@ -1431,7 +1449,7 @@ const SewagePipeInp = () => {
|
||||
sewer_bzmn_cd: values?.bsnmCd ?? '',
|
||||
stats_cd: statsCdForTab,
|
||||
stats_yr: values.year ?? "",
|
||||
lae_sbmsn_stts_cd: statusForTab?.laeSbmsnSttsCd === "4" ? "4" : "2",
|
||||
lae_sbmsn_stts_cd: statusForTab?.lclgvSbmsnSttsCd === "4" ? "4" : "2",
|
||||
gen_verify_stts_cd: nvpassed ? "1" : "2",
|
||||
hb_pass_yn: response.data?.isHbPassed,
|
||||
};
|
||||
@@ -1533,7 +1551,7 @@ const SewagePipeInp = () => {
|
||||
...toInputSearchRegion(sidoListRef.current, searchValues),
|
||||
ctpvNm: searchValues?.sido ?? '',
|
||||
statsYr: searchValues?.year ?? '',
|
||||
mdfrId: userInfo.userUnqId,
|
||||
mdfrUuid: userInfo.userUnqId,
|
||||
statsCd: tabStatsCdMap.get(activeTab) ?? statsCd,
|
||||
};
|
||||
|
||||
@@ -1617,7 +1635,6 @@ const SewagePipeInp = () => {
|
||||
|
||||
const saveList = currentData.map((d) => ({
|
||||
admdstCd: nullableTrimmedString(d.admdstCd),
|
||||
sggNm: nullableTrimmedString(d.sggNm),
|
||||
admdstNm: nullableTrimmedString(d.admdstNm),
|
||||
goalYr: nullableTrimmedString(d.goalYr),
|
||||
wholPplMtncPlanPrlg: formatNumber(d.wholPplMtncPlanPrlg, "wholPplMtncPlanPrlg"),
|
||||
@@ -1628,29 +1645,29 @@ const SewagePipeInp = () => {
|
||||
cbsysPplMtncPlanPrlg: formatNumber(d.cbsysPplMtncPlanPrlg, "cbsysPplMtncPlanPrlg"),
|
||||
cbsysPlanPrlg: formatNumber(d.cbsysPlanPrlg, "cbsysPlanPrlg"),
|
||||
cbsysFcltPrlg: formatNumber(d.cbsysFcltPrlg, "cbsysFcltPrlg"),
|
||||
cbsysSqrePrlg: formatNumber(d.cbsysSqrePrlg, "cbsysSqrePrlg"),
|
||||
cbsysCiclPrlg: formatNumber(d.cbsysCiclPrlg, "cbsysCiclPrlg"),
|
||||
cbsysShpPrlg: formatNumber(d.cbsysShpPrlg, "cbsysShpPrlg"),
|
||||
cbsysElpsdPrlg: formatNumber(d.cbsysElpsdPrlg, "cbsysElpsdPrlg"),
|
||||
cbsysOpnPrlg: formatNumber(d.cbsysOpnPrlg, "cbsysOpnPrlg"),
|
||||
cbsysGttrPrlg: formatNumber(d.cbsysGttrPrlg, "cbsysGttrPrlg"),
|
||||
cbsysLaditPrlg: formatNumber(d.cbsysLaditPrlg, "cbsysLaditPrlg"),
|
||||
drtwPplMtncPlanArea: formatNumber(d.drtwPplMtncPlanArea, "drtwPplMtncPlanArea"),
|
||||
drtwPplMtncPlanPrlg: formatNumber(d.drtwPplMtncPlanPrlg, "drtwPplMtncPlanPrlg"),
|
||||
drtwPplPlanPrlg: formatNumber(d.drtwPplPlanPrlg, "drtwPplPlanPrlg"),
|
||||
drtwPplFcltPrlg: formatNumber(d.drtwPplFcltPrlg, "drtwPplFcltPrlg"),
|
||||
drtwPplSqrePrlg: formatNumber(d.drtwPplSqrePrlg, "drtwPplSqrePrlg"),
|
||||
drtwPplCiclPrlg: formatNumber(d.drtwPplCiclPrlg, "drtwPplCiclPrlg"),
|
||||
drtwPplShpPrlg: formatNumber(d.drtwPplShpPrlg, "drtwPplShpPrlg"),
|
||||
drtwPplElpsdPrlg: formatNumber(d.drtwPplElpsdPrlg, "drtwPplElpsdPrlg"),
|
||||
exlnPplMtncPlanArea: formatNumber(d.exlnPplMtncPlanArea, "exlnPplMtncPlanArea"),
|
||||
exlnPplMtncPlanPrlg: formatNumber(d.exlnPplMtncPlanPrlg, "exlnPplMtncPlanPrlg"),
|
||||
exlnPplPlanPrlg: formatNumber(d.exlnPplPlanPrlg, "exlnPplPlanPrlg"),
|
||||
exlnPplFcltPrlg: formatNumber(d.exlnPplFcltPrlg, "exlnPplFcltPrlg"),
|
||||
exlnPplSqrePrlg: formatNumber(d.exlnPplSqrePrlg, "exlnPplSqrePrlg"),
|
||||
exlnPplCiclPrlg: formatNumber(d.exlnPplCiclPrlg, "exlnPplCiclPrlg"),
|
||||
exlnPplShpPrlg: formatNumber(d.exlnPplShpPrlg, "exlnPplShpPrlg"),
|
||||
exlnPplElpsdPrlg: formatNumber(d.exlnPplElpsdPrlg, "exlnPplElpsdPrlg"),
|
||||
exlnPplOpnPrlg: formatNumber(d.exlnPplOpnPrlg, "exlnPplOpnPrlg"),
|
||||
exlnPplGttrPrlg: formatNumber(d.exlnPplGttrPrlg, "exlnPplGttrPrlg"),
|
||||
exlnPplLaditPrlg: formatNumber(d.exlnPplLaditPrlg, "exlnPplLaditPrlg"),
|
||||
cbsysMnhlCnt: formatNumber(d.cbsysMnhlCnt, "cbsysMnhlCnt"),
|
||||
sprsysDrtwMnhlCnt: formatNumber(d.sprsysDrtwMnhlCnt, "sprsysDrtwMnhlCnt"),
|
||||
sprsysExlnMnhlCnt: formatNumber(d.sprsysExlnMnhlCnt, "sprsysExlnMnhlCnt"),
|
||||
drtwRcvprpCnt: formatNumber(d.drtwRcvprpCnt, "drtwRcvprpCnt"),
|
||||
exlnRcvprpCnt: formatNumber(d.exlnRcvprpCnt, "exlnRcvprpCnt"),
|
||||
drtwDrgCnt: formatNumber(d.drtwDrgCnt, "drtwDrgCnt"),
|
||||
exlnDrgCnt: formatNumber(d.exlnDrgCnt, "exlnDrgCnt"),
|
||||
stofcbrCnt: formatNumber(d.stofcbrCnt, "stofcbrCnt"),
|
||||
exlnOtflCnt: formatNumber(d.exlnOtflCnt, "exlnOtflCnt"),
|
||||
sewerLdgrEdpPrlg: formatNumber(d.sewerLdgrEdpPrlg, "sewerLdgrEdpPrlg"),
|
||||
@@ -1662,7 +1679,7 @@ const SewagePipeInp = () => {
|
||||
statsYr: values.year ?? "",
|
||||
ctpvNm: values.sido ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, values),
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
||||
saveList,
|
||||
};
|
||||
@@ -1708,20 +1725,19 @@ const SewagePipeInp = () => {
|
||||
|
||||
const saveList = currentData.map((d) => ({
|
||||
admdstCd: nullableTrimmedString(d.admdstCd),
|
||||
sggNm: nullableTrimmedString(d.sggNm),
|
||||
admdstNm: nullableTrimmedString(d.admdstNm),
|
||||
fcltSn: nullableTrimmedString(d.fcltSn),
|
||||
swtrmtFcltCd: nullableTrimmedString(d.swtrmtFcltCd),
|
||||
swtrmtFcltNm: nullableTrimmedString(d.swtrmtFcltNm),
|
||||
prcsZoneArea: formatNumber(d.prcsZoneArea, "prcsZoneArea"),
|
||||
cbsysPrcsZoneArea: formatNumber(d.cbsysPrcsZoneArea, "cbsysPrcsZoneArea"),
|
||||
sprsysPrcsZoneArea: formatNumber(d.sprsysPrcsZoneArea, "sprsysPrcsZoneArea"),
|
||||
cbsysTrtznArea: formatNumber(d.cbsysTrtznArea, "cbsysTrtznArea"),
|
||||
sprsysTrtznArea: formatNumber(d.sprsysTrtznArea, "sprsysTrtznArea"),
|
||||
sprsysVartnRt: formatNumber(d.sprsysVartnRt, "sprsysVartnRt"),
|
||||
mltvEqpmCnt: formatNumber(d.mltvEqpmCnt, "mltvEqpmCnt"),
|
||||
cbsysMltvEqpmCnt: formatNumber(d.cbsysMltvEqpmCnt, "cbsysMltvEqpmCnt"),
|
||||
sprsysMltvEqpmCnt: formatNumber(d.sprsysMltvEqpmCnt, "sprsysMltvEqpmCnt"),
|
||||
sprsysMltvLnkgCnt: formatNumber(d.sprsysMltvLnkgCnt, "sprsysMltvLnkgCnt"),
|
||||
sprsysMltvYetLnkgCnt: formatNumber(d.sprsysMltvYetLnkgCnt, "sprsysMltvYetLnkgCnt"),
|
||||
wholDrgCnt: formatNumber(d.wholDrgCnt, "wholDrgCnt"),
|
||||
cbsysDrgCnt: formatNumber(d.cbsysDrgCnt, "cbsysDrgCnt"),
|
||||
sprsysDrgCnt: formatNumber(d.sprsysDrgCnt, "sprsysDrgCnt"),
|
||||
sprsysDrgLnkgCnt: formatNumber(d.sprsysDrgLnkgCnt, "sprsysDrgLnkgCnt"),
|
||||
sprsysDrgNoinsLnkgCnt: formatNumber(d.sprsysDrgNoinsLnkgCnt, "sprsysDrgNoinsLnkgCnt"),
|
||||
wholFcltPrlg: formatNumber(d.wholFcltPrlg, "wholFcltPrlg"),
|
||||
wholIcptgAtlfcsPrlg: formatNumber(d.wholIcptgAtlfcsPrlg, "wholIcptgAtlfcsPrlg"),
|
||||
icptgPrlg: formatNumber(d.icptgPrlg, "icptgPrlg"),
|
||||
@@ -1729,10 +1745,10 @@ const SewagePipeInp = () => {
|
||||
cbsysStofcbrCnt: formatNumber(d.cbsysStofcbrCnt, "cbsysStofcbrCnt"),
|
||||
sprsysStofcbrCnt: formatNumber(d.sprsysStofcbrCnt, "sprsysStofcbrCnt"),
|
||||
sprsysExlnOtflCnt: formatNumber(d.sprsysExlnOtflCnt, "sprsysExlnOtflCnt"),
|
||||
wholBrchlnPrlg: formatNumber(d.wholBrchlnPrlg, "wholBrchlnPrlg"),
|
||||
cbsysBrchlnPrlg: formatNumber(d.cbsysBrchlnPrlg, "cbsysBrchlnPrlg"),
|
||||
exlnBrchlnPrlg: formatNumber(d.exlnBrchlnPrlg, "exlnBrchlnPrlg"),
|
||||
drtwBrchlnPrlg: formatNumber(d.drtwBrchlnPrlg, "drtwBrchlnPrlg"),
|
||||
wholPplPrlg: formatNumber(d.wholPplPrlg, "wholPplPrlg"),
|
||||
cbsysPplPrlg: formatNumber(d.cbsysPplPrlg, "cbsysPplPrlg"),
|
||||
exlnPplPrlg: formatNumber(d.exlnPplPrlg, "exlnPplPrlg"),
|
||||
drtwPplPrlg: formatNumber(d.drtwPplPrlg, "drtwPplPrlg"),
|
||||
rmrk: nullableTrimmedString(d.rmrk),
|
||||
}));
|
||||
|
||||
@@ -1740,7 +1756,7 @@ const SewagePipeInp = () => {
|
||||
statsYr: values.year ?? "",
|
||||
ctpvNm: values.sido ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, values),
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
||||
saveList,
|
||||
};
|
||||
@@ -1800,7 +1816,7 @@ const SewagePipeInp = () => {
|
||||
d[dmltnKey] as string | number | undefined,
|
||||
dmltnKey,
|
||||
),
|
||||
prlgWhol: formatNumber(
|
||||
pipeWholPrlg: formatNumber(
|
||||
d[wholKey] as string | number | undefined,
|
||||
wholKey,
|
||||
),
|
||||
@@ -1824,7 +1840,7 @@ const SewagePipeInp = () => {
|
||||
statsYr: values.year ?? "",
|
||||
ctpvNm: values.sido ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, values),
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
||||
saveList,
|
||||
};
|
||||
@@ -1867,15 +1883,15 @@ const SewagePipeInp = () => {
|
||||
|
||||
const saveList = currentData.map((d) => ({
|
||||
pipeSeCd: nullableTrimmedString(d.pipeSeCd),
|
||||
step1PdmtPrlg: formatNumber(d.step1PdmtPrlg, "step1PdmtPrlg"),
|
||||
step2PdmtPrlg: formatNumber(d.step2PdmtPrlg, "step2PdmtPrlg"),
|
||||
step3PdmtPrlg: formatNumber(d.step3PdmtPrlg, "step3PdmtPrlg"),
|
||||
step4PdmtPrlg: formatNumber(d.step4PdmtPrlg, "step4PdmtPrlg"),
|
||||
step5PdmtPrlg: formatNumber(d.step5PdmtPrlg, "step5PdmtPrlg"),
|
||||
sqrePdmtPrlg: formatNumber(d.sqrePdmtPrlg, "sqrePdmtPrlg"),
|
||||
frlvlPdmtPrlg: formatNumber(d.frlvlPdmtPrlg, "frlvlPdmtPrlg"),
|
||||
sclvlPdmtPrlg: formatNumber(d.sclvlPdmtPrlg, "sclvlPdmtPrlg"),
|
||||
thlvlPdmtPrlg: formatNumber(d.thlvlPdmtPrlg, "thlvlPdmtPrlg"),
|
||||
folvlPdmtPrlg: formatNumber(d.folvlPdmtPrlg, "folvlPdmtPrlg"),
|
||||
filvlPdmtPrlg: formatNumber(d.filvlPdmtPrlg, "filvlPdmtPrlg"),
|
||||
shpPdmtPrlg: formatNumber(d.shpPdmtPrlg, "shpPdmtPrlg"),
|
||||
etcPdmtPrlg: formatNumber(d.etcPdmtPrlg, "etcPdmtPrlg"),
|
||||
rmrk: nullableTrimmedString(d.rmrk),
|
||||
pdmtByTsmWhol: formatNumber(d.pdmtByTsmWhol, "pdmtByTsmWhol"),
|
||||
pdmtSeWholPrlg: formatNumber(d.pdmtSeWholPrlg, "pdmtSeWholPrlg"),
|
||||
admdstCd: nullableTrimmedString(d.admdstCd),
|
||||
admdstNm: nullableTrimmedString(d.admdstNm),
|
||||
}));
|
||||
@@ -1886,7 +1902,7 @@ const SewagePipeInp = () => {
|
||||
statsYr: values.year ?? "",
|
||||
ctpvNm: values.sido ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, values),
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
||||
saveList,
|
||||
};
|
||||
@@ -1944,7 +1960,7 @@ const SewagePipeInp = () => {
|
||||
statsYr: values.year ?? "",
|
||||
ctpvNm: values.sido ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, values),
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
||||
saveList,
|
||||
};
|
||||
@@ -2025,7 +2041,7 @@ const SewagePipeInp = () => {
|
||||
{submitOk && <SubmitButton submitFunc={() => void submitFunc(false)} />}
|
||||
</>
|
||||
)}
|
||||
{activeTabStatus?.laeSbmsnSttsCd === "2" && submitOk && (
|
||||
{activeTabStatus?.lclgvSbmsnSttsCd === "2" && submitOk && (
|
||||
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
|
||||
)}
|
||||
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
|
||||
@@ -2200,8 +2216,11 @@ const SewagePipeInp = () => {
|
||||
}
|
||||
cellStyleCallback={({ row, fieldName }) => {
|
||||
const verification = (row as GridRowWithVerification).verificationResult;
|
||||
if (!verification) return;
|
||||
const errors = verification[fieldName];
|
||||
const errors = getSewagePipeRowVerificationErrors(
|
||||
verification,
|
||||
fieldName,
|
||||
tabKey,
|
||||
);
|
||||
if (Array.isArray(errors) && errors.length > 0) {
|
||||
const hasImr = errors.some((e) => e.vrfcCd === "IMR" || e.vrfcCd === "HB");
|
||||
return hasImr ? "rg-cell-verify-error-imr" : "rg-cell-verify-error";
|
||||
@@ -2212,15 +2231,18 @@ const SewagePipeInp = () => {
|
||||
}
|
||||
cellTooltipCallback={({ row, fieldName }) => {
|
||||
const verification = (row as GridRowWithVerification).verificationResult;
|
||||
if (!verification) return;
|
||||
const errors = verification[fieldName];
|
||||
const errors = getSewagePipeRowVerificationErrors(
|
||||
verification,
|
||||
fieldName,
|
||||
tabKey,
|
||||
);
|
||||
if (Array.isArray(errors) && errors.length > 0) {
|
||||
return errors
|
||||
.map((e) => {
|
||||
const prefix =
|
||||
e.vrfcCd === "IMR" ? "시계열 검증 - " : (e.vrfcCd === "NV" ? "일반검증 - " : "모집단 검증 - ");
|
||||
const comp = e.compVlCn ?? "";
|
||||
const now = e.nowVlCn ?? "";
|
||||
const comp = e.cmprCn ?? "";
|
||||
const now = e.nowCn ?? "";
|
||||
return `${prefix}${e.rsnExpln}
|
||||
(검증 기준값: ${comp} / 현재 입력값: ${now})`;
|
||||
})
|
||||
|
||||
@@ -113,8 +113,8 @@ const computeTabEnterable = (
|
||||
statusInfo: SewerageStatusInfo | null | undefined,
|
||||
periodEnterable: boolean,
|
||||
): boolean => {
|
||||
if (statusInfo?.laeSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.laeSbmsnSttsCd)) {
|
||||
if (statusInfo?.lclgvSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.lclgvSbmsnSttsCd)) {
|
||||
return false;
|
||||
}
|
||||
return periodEnterable;
|
||||
@@ -142,7 +142,7 @@ function computeTabToolbarSnapshot(
|
||||
): TabToolbarSnapshot {
|
||||
const gugunOk = buildGugunOkForToolbar(searchContext);
|
||||
const enterable = computeTabEnterable(statusInfo, periodEnterable);
|
||||
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2";
|
||||
const isSubmitted = statusInfo?.lclgvSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
|
||||
const showEnterableToolbarButtons = enterable && gugunOk;
|
||||
const showSubmittedInInputPeriodToolbar =
|
||||
@@ -218,20 +218,20 @@ function buildRepairRowList(
|
||||
|
||||
return currentData.map((d) => ({
|
||||
admdstCd: nullableTrimmedString(d.admdstCd),
|
||||
sggNm: nullableTrimmedString(d.sggNm),
|
||||
admdstNm: nullableTrimmedString(d.admdstNm),
|
||||
mendngWhol: formatNumber(d.mendngWhol, "mendngWhol"),
|
||||
cbsysMendngPrlg: formatNumber(d.cbsysMendngPrlg, "cbsysMendngPrlg"),
|
||||
dfrpWholPrlg: formatNumber(d.dfrpWholPrlg, "dfrpWholPrlg"),
|
||||
cbsysDfrpPrlg: formatNumber(d.cbsysDfrpPrlg, "cbsysDfrpPrlg"),
|
||||
sprsysExlnPrlg: formatNumber(d.sprsysExlnPrlg, "sprsysExlnPrlg"),
|
||||
sprsysDrtwPrlg: formatNumber(d.sprsysDrtwPrlg, "sprsysDrtwPrlg"),
|
||||
cbsysPartMendngPrlg: formatNumber(d.cbsysPartMendngPrlg, "cbsysPartMendngPrlg"),
|
||||
sprsysExlnMendngPrlg: formatNumber(d.sprsysExlnMendngPrlg, "sprsysExlnMendngPrlg"),
|
||||
sprsysDrtwMendngPrlg: formatNumber(d.sprsysDrtwMendngPrlg, "sprsysDrtwMendngPrlg"),
|
||||
partDfrpWhol: formatNumber(d.partDfrpWhol, "partDfrpWhol"),
|
||||
cbsysPartDfrpPrlg: formatNumber(d.cbsysPartDfrpPrlg, "cbsysPartDfrpPrlg"),
|
||||
sprsysExlnDfrpPrlg: formatNumber(d.sprsysExlnDfrpPrlg, "sprsysExlnDfrpPrlg"),
|
||||
sprsysDrtwDfrpPrlg: formatNumber(d.sprsysDrtwDfrpPrlg, "sprsysDrtwDfrpPrlg"),
|
||||
cbsysMnhlCnt: formatNumber(d.cbsysMnhlCnt, "cbsysMnhlCnt"),
|
||||
sprsysDrtwMnhlCnt: formatNumber(d.sprsysDrtwMnhlCnt, "sprsysDrtwMnhlCnt"),
|
||||
sprsysExlnMnhlCnt: formatNumber(d.sprsysExlnMnhlCnt, "sprsysExlnMnhlCnt"),
|
||||
drtwRcvprpCnt: formatNumber(d.drtwRcvprpCnt, "drtwRcvprpCnt"),
|
||||
exlnRcvprpCnt: formatNumber(d.exlnRcvprpCnt, "exlnRcvprpCnt"),
|
||||
drtwDrgCnt: formatNumber(d.drtwDrgCnt, "drtwDrgCnt"),
|
||||
exlnDrgCnt: formatNumber(d.exlnDrgCnt, "exlnDrgCnt"),
|
||||
stofcbrCnt: formatNumber(d.stofcbrCnt, "stofcbrCnt"),
|
||||
otflCnt: formatNumber(d.otflCnt, "otflCnt"),
|
||||
cctvExmnPrlg: formatNumber(d.cctvExmnPrlg, "cctvExmnPrlg"),
|
||||
@@ -246,7 +246,6 @@ function buildDredgingRowList(
|
||||
|
||||
return currentData.map((d) => ({
|
||||
admdstCd: nullableTrimmedString(d.admdstCd),
|
||||
sggNm: nullableTrimmedString(d.sggNm),
|
||||
admdstNm: nullableTrimmedString(d.admdstNm),
|
||||
wholFcltPrlg: formatNumber(d.wholFcltPrlg, "wholFcltPrlg"),
|
||||
wholSwagePpl: formatNumber(d.wholSwagePpl, "wholSwagePpl"),
|
||||
@@ -254,14 +253,14 @@ function buildDredgingRowList(
|
||||
sprsysExlnPrlg: formatNumber(d.sprsysExlnPrlg, "sprsysExlnPrlg"),
|
||||
sprsysDrtwPrlg: formatNumber(d.sprsysDrtwPrlg, "sprsysDrtwPrlg"),
|
||||
mnhlCnt: formatNumber(d.mnhlCnt, "mnhlCnt"),
|
||||
exlnDrtwRcvprpCnt: formatNumber(d.exlnDrtwRcvprpCnt, "exlnDrtwRcvprpCnt"),
|
||||
exlnDrtwDrgCnt: formatNumber(d.exlnDrtwDrgCnt, "exlnDrtwDrgCnt"),
|
||||
stofcbrOtflCnt: formatNumber(d.stofcbrOtflCnt, "stofcbrOtflCnt"),
|
||||
drdgnPlanPrlg: formatNumber(d.drdgnPlanPrlg, "drdgnPlanPrlg"),
|
||||
drdgnTrgtCtrsDrdgnRt: formatNumber(d.drdgnTrgtCtrsDrdgnRt, "drdgnTrgtCtrsDrdgnRt"),
|
||||
wholPrlgCtrsDrdgnRt: formatNumber(d.wholPrlgCtrsDrdgnRt, "wholPrlgCtrsDrdgnRt"),
|
||||
drdgnAmt: formatNumber(d.drdgnAmt, "drdgnAmt"),
|
||||
drdgnQnt: formatNumber(d.drdgnQnt, "drdgnQnt"),
|
||||
drdgnUntprcAmt: formatNumber(d.drdgnUntprcAmt, "drdgnUntprcAmt"),
|
||||
pplDrdqty: formatNumber(d.pplDrdqty, "pplDrdqty"),
|
||||
drdgnSoilPrcsAmt: formatNumber(d.drdgnSoilPrcsAmt, "drdgnSoilPrcsAmt"),
|
||||
wholDrdgnPrcsQnt: formatNumber(d.wholDrdgnPrcsQnt, "wholDrdgnPrcsQnt"),
|
||||
rclmQnt: formatNumber(d.rclmQnt, "rclmQnt"),
|
||||
oceanEmsnQnt: formatNumber(d.oceanEmsnQnt, "oceanEmsnQnt"),
|
||||
@@ -613,14 +612,14 @@ const SewagePipeMaintainInp = () => {
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
laePicNm: submitInfo.laePicNm,
|
||||
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm,
|
||||
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm,
|
||||
laePicTelNo: submitInfo.laePicTelNo,
|
||||
laeIdfrNm: submitInfo.laeIdfrNm,
|
||||
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
|
||||
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
|
||||
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
|
||||
lclgvPicNm: submitInfo.lclgvPicNm,
|
||||
lclgvKbrdrJbgdNm: submitInfo.lclgvKbrdrJbgdNm,
|
||||
lclgvKbrdrDeptNm: submitInfo.lclgvKbrdrDeptNm,
|
||||
lclgvPicTelnoCn: submitInfo.lclgvPicTelnoCn,
|
||||
lclgvIdfrNm: submitInfo.lclgvIdfrNm,
|
||||
lclgvIdfrJbgdNm: submitInfo.lclgvIdfrJbgdNm,
|
||||
lclgvIdfrDeptNm: submitInfo.lclgvIdfrDeptNm,
|
||||
lclgvIdfrTelnoCn: submitInfo.lclgvIdfrTelnoCn,
|
||||
};
|
||||
|
||||
const verificationQuery = {
|
||||
@@ -648,12 +647,12 @@ const SewagePipeMaintainInp = () => {
|
||||
const response = isRepairTab
|
||||
? await sewagePipeMaintainApiService.submitSewagePipeMaintainRepair({
|
||||
...submitInfoBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: buildRepairRowList(currentData),
|
||||
} satisfies SewagePipeMaintainRepairSubmitRequest)
|
||||
: await sewagePipeMaintainApiService.submitSewagePipeMaintainDredging({
|
||||
...submitInfoBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: buildDredgingRowList(currentData),
|
||||
} satisfies SewagePipeMaintainDredgingSubmitRequest);
|
||||
|
||||
@@ -673,7 +672,7 @@ const SewagePipeMaintainInp = () => {
|
||||
sewer_bzmn_cd: lastSearchedValues?.bsnmCd ?? "",
|
||||
stats_cd: statsCdForTab,
|
||||
stats_yr: lastSearchedValues.year ?? "",
|
||||
lae_sbmsn_stts_cd: activeTabStatus?.laeSbmsnSttsCd === "4" ? "4" : "2",
|
||||
lae_sbmsn_stts_cd: activeTabStatus?.lclgvSbmsnSttsCd === "4" ? "4" : "2",
|
||||
gen_verify_stts_cd: nvpassed ? "1" : "2",
|
||||
hb_pass_yn: response.data?.isHbPassed,
|
||||
};
|
||||
@@ -767,7 +766,7 @@ const SewagePipeMaintainInp = () => {
|
||||
|
||||
const param: SewagePipeMaintainRepairSaveRequest = {
|
||||
...baseRequest,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
saveList: buildRepairRowList(currentData),
|
||||
};
|
||||
if (isAdminInfo) {
|
||||
@@ -804,7 +803,7 @@ const SewagePipeMaintainInp = () => {
|
||||
|
||||
const param: SewagePipeMaintainDredgingSaveRequest = {
|
||||
...baseRequest,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
saveList: buildDredgingRowList(currentData),
|
||||
};
|
||||
if (isAdminInfo) {
|
||||
@@ -856,7 +855,7 @@ const SewagePipeMaintainInp = () => {
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
mdfrId: userInfo.userUnqId,
|
||||
mdfrUuid: userInfo.userUnqId,
|
||||
statsCd: statsCdForTab,
|
||||
};
|
||||
|
||||
@@ -1108,7 +1107,7 @@ const SewagePipeMaintainInp = () => {
|
||||
.map((e) => {
|
||||
const prefix =
|
||||
e.vrfcCd === "IMR" ? "시계열 검증 - " : e.vrfcCd === "NV" ? "일반검증 - " : "모집단 검증 - ";
|
||||
return `${prefix}${e.rsnExpln}(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`;
|
||||
return `${prefix}${e.rsnExpln}(검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
@@ -1143,7 +1142,7 @@ const SewagePipeMaintainInp = () => {
|
||||
{submitOk && <SubmitButton submitFunc={submitFunc} />}
|
||||
</>
|
||||
)}
|
||||
{activeTabStatus?.laeSbmsnSttsCd === "2" && submitOk && (
|
||||
{activeTabStatus?.lclgvSbmsnSttsCd === "2" && submitOk && (
|
||||
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
|
||||
)}
|
||||
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
|
||||
|
||||
@@ -125,8 +125,8 @@ const computeTabEnterable = (
|
||||
statusInfo: SewerageStatusInfo | null | undefined,
|
||||
periodEnterable: boolean,
|
||||
): boolean => {
|
||||
if (statusInfo?.laeSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.laeSbmsnSttsCd)) {
|
||||
if (statusInfo?.lclgvSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.lclgvSbmsnSttsCd)) {
|
||||
return false;
|
||||
}
|
||||
return periodEnterable;
|
||||
@@ -162,7 +162,7 @@ function computeTabToolbarSnapshot(
|
||||
): TabToolbarSnapshot {
|
||||
const gugunOk = buildGugunOkForToolbar(searchContext);
|
||||
const enterable = computeTabEnterable(statusInfo, periodEnterable);
|
||||
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2";
|
||||
const isSubmitted = statusInfo?.lclgvSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
|
||||
const showEnterableToolbarButtons = enterable && gugunOk;
|
||||
const showSubmittedInInputPeriodToolbar =
|
||||
@@ -274,7 +274,7 @@ function mapSewageReservoirListToGridRows(
|
||||
ctpvNm,
|
||||
oprtngStrtYmd: formatCompactYmdForGrid(item.oprtngStrtYmd),
|
||||
fldngPrvtnYn: normalizeOxYnValue(item.fldngPrvtnYn),
|
||||
wqPlltnMtgtYn: normalizeOxYnValue(item.wqPlltnMtgtYn),
|
||||
wtpltMtgtYn: normalizeOxYnValue(item.wtpltMtgtYn),
|
||||
rtlztnUsgYn: normalizeOxYnValue(item.rtlztnUsgYn),
|
||||
},
|
||||
])[0] as GridRowWithVerification;
|
||||
@@ -332,37 +332,37 @@ function buildSewageReservoirSaveItem(
|
||||
oprtngStrtYmd: toCompactYmdOrNull(row.oprtngStrtYmd),
|
||||
planDesignFreqCnt: toGridNumber(row.planDesignFreqCnt),
|
||||
fldngPrvtnYn: nullableTrimmedString(normalizeOxYnValue(row.fldngPrvtnYn)),
|
||||
wqPlltnMtgtYn: nullableTrimmedString(normalizeOxYnValue(row.wqPlltnMtgtYn)),
|
||||
wtpltMtgtYn: nullableTrimmedString(normalizeOxYnValue(row.wtpltMtgtYn)),
|
||||
rtlztnUsgYn: nullableTrimmedString(normalizeOxYnValue(row.rtlztnUsgYn)),
|
||||
fcltStrctCn: nullableTrimmedString(row.fcltStrctCn),
|
||||
wtrStrgZoneArea: toGridNumber(row.wtrStrgZoneArea),
|
||||
impdFcltCpct: toGridNumber(row.impdFcltCpct),
|
||||
grssPpqty: toGridNumber(row.grssPpqty),
|
||||
wholPpqty: toGridNumber(row.wholPpqty),
|
||||
pumpFcltN1DmtrExpln: toPumpDmtrExpln(row.pumpFcltN1DmtrExpln),
|
||||
pumpFcltN1PerPpqty: toGridNumber(row.pumpFcltN1PerPpqty),
|
||||
pumpFcltN1Ppqty: toGridNumber(row.pumpFcltN1Ppqty),
|
||||
pumpFcltN1PumpCnt: toGridNumber(row.pumpFcltN1PumpCnt),
|
||||
pumpFcltN1RsrvPumpCnt: toGridNumber(row.pumpFcltN1RsrvPumpCnt),
|
||||
pumpFcltN2DmtrExpln: toPumpDmtrExpln(row.pumpFcltN2DmtrExpln),
|
||||
pumpFcltN2PerPpqty: toGridNumber(row.pumpFcltN2PerPpqty),
|
||||
pumpFcltN2Ppqty: toGridNumber(row.pumpFcltN2Ppqty),
|
||||
pumpFcltN2PumpCnt: toGridNumber(row.pumpFcltN2PumpCnt),
|
||||
pumpFcltN2RsrvPumpCnt: toGridNumber(row.pumpFcltN2RsrvPumpCnt),
|
||||
pumpFcltN3DmtrExpln: toPumpDmtrExpln(row.pumpFcltN3DmtrExpln),
|
||||
pumpFcltN3PerPpqty: toGridNumber(row.pumpFcltN3PerPpqty),
|
||||
pumpFcltN3Ppqty: toGridNumber(row.pumpFcltN3Ppqty),
|
||||
pumpFcltN3PumpCnt: toGridNumber(row.pumpFcltN3PumpCnt),
|
||||
pumpFcltN3RsrvPumpCnt: toGridNumber(row.pumpFcltN3RsrvPumpCnt),
|
||||
prcsgFcltYn: row.prcsgFcltYn ? (row.prcsgFcltYn === "무" ? "N" : "Y") : null,
|
||||
ddrztnEqpmYn: row.ddrztnEqpmYn ? (row.prcsgFcltYn === "무" ? "N" : "Y") : null,
|
||||
odorEqpmYn: row.odorEqpmYn ? (row.prcsgFcltYn === "무" ? "N" : "Y") : null,
|
||||
oslfEtgtYn: row.oslfEtgtYn ? (row.prcsgFcltYn === "무" ? "N" : "Y") : null,
|
||||
mltvMthCn: nullableTrimmedString(row.mltvMthCn),
|
||||
drgptnCn: nullableTrimmedString(row.drgptnCn),
|
||||
impdMthCn: nullableTrimmedString(row.impdMthCn),
|
||||
impdPrcsMthdCn: nullableTrimmedString(row.impdPrcsMthdCn),
|
||||
impdPrcsCn: nullableTrimmedString(row.impdPrcsCn),
|
||||
dschgWtrsExpln: nullableTrimmedString(row.dschgWtrsExpln),
|
||||
prcptnLastDspsQnt: toGridNumber(row.prcptnLastDspsQnt),
|
||||
prpwLastDspsQnt: toGridNumber(row.prpwLastDspsQnt),
|
||||
ctmnLastDspsQnt: toGridNumber(row.ctmnLastDspsQnt),
|
||||
prcptnLastDspsMthdExpln: nullableTrimmedString(row.prcptnLastDspsMthdExpln),
|
||||
ctmnLastDspsMthdExpln: nullableTrimmedString(row.ctmnLastDspsMthdExpln),
|
||||
prpwLastDspsExpln: nullableTrimmedString(row.prpwLastDspsExpln),
|
||||
ctmnLastDspsExpln: nullableTrimmedString(row.ctmnLastDspsExpln),
|
||||
oprtngDayCnt: toGridNumber(row.oprtngDayCnt),
|
||||
grssMltvQnt: toGridNumber(row.grssMltvQnt),
|
||||
wholQnt: toGridNumber(row.wholQnt),
|
||||
rmrk: nullableTrimmedString(row.rmrk),
|
||||
};
|
||||
}
|
||||
@@ -386,32 +386,32 @@ function buildRainfallPumpStationSaveItem(
|
||||
oprtngStrtYmd: toCompactYmdOrNull(row.oprtngStrtYmd),
|
||||
planDesignFreqCnt: toGridNumber(row.planDesignFreqCnt),
|
||||
wtrStrgZoneArea: toGridNumber(row.wtrStrgZoneArea),
|
||||
rsvrYn: row.rsvrYn ? (row.rsvrYn === "무" ? "N" : "Y") : null,
|
||||
rsvrImpndQnt: toGridNumber(row.rsvrImpndQnt),
|
||||
runwtrYn: row.runwtrYn ? (row.runwtrYn === "무" ? "N" : "Y") : null,
|
||||
runwtrStrqty: toGridNumber(row.runwtrStrqty),
|
||||
abstnRgnImpdQnt: toGridNumber(row.abstnRgnImpdQnt),
|
||||
grssPpqty: toGridNumber(row.grssPpqty),
|
||||
wholPpqty: toGridNumber(row.wholPpqty),
|
||||
pumpFcltN1DmtrExpln: toPumpDmtrExpln(row.pumpFcltN1DmtrExpln),
|
||||
pumpFcltN1PerPpqty: toGridNumber(row.pumpFcltN1PerPpqty),
|
||||
pumpFcltN1UnitPpqty: toGridNumber(row.pumpFcltN1UnitPpqty),
|
||||
pumpFcltN1PumpCnt: toGridNumber(row.pumpFcltN1PumpCnt),
|
||||
pumpFcltN1RsrvPumpCnt: toGridNumber(row.pumpFcltN1RsrvPumpCnt),
|
||||
pumpFcltN2DmtrExpln: toPumpDmtrExpln(row.pumpFcltN2DmtrExpln),
|
||||
pumpFcltN2PerPpqty: toGridNumber(row.pumpFcltN2PerPpqty),
|
||||
pumpFcltN2UnitPpqty: toGridNumber(row.pumpFcltN2UnitPpqty),
|
||||
pumpFcltN2PumpCnt: toGridNumber(row.pumpFcltN2PumpCnt),
|
||||
pumpFcltN2RsrvPumpCnt: toGridNumber(row.pumpFcltN2RsrvPumpCnt),
|
||||
pumpFcltN3DmtrExpln: toPumpDmtrExpln(row.pumpFcltN3DmtrExpln),
|
||||
pumpFcltN3PerPpqty: toGridNumber(row.pumpFcltN3PerPpqty),
|
||||
pumpFcltN3UnitPpqty: toGridNumber(row.pumpFcltN3UnitPpqty),
|
||||
pumpFcltN3PumpCnt: toGridNumber(row.pumpFcltN3PumpCnt),
|
||||
pumpFcltN3RsrvPumpCnt: toGridNumber(row.pumpFcltN3RsrvPumpCnt),
|
||||
prcsgFcltYn: row.prcsgFcltYn ? (row.prcsgFcltYn === "무" ? "N" : "Y") : null,
|
||||
ddrztnEqpmYn: row.ddrztnEqpmYn ? (row.prcsgFcltYn === "무" ? "N" : "Y") : null,
|
||||
odorEqpmYn: row.odorEqpmYn ? (row.prcsgFcltYn === "무" ? "N" : "Y") : null,
|
||||
oslfEtgtYn: row.oslfEtgtYn ? (row.prcsgFcltYn === "무" ? "N" : "Y") : null,
|
||||
dschgWtrsExpln: nullableTrimmedString(row.dschgWtrsExpln),
|
||||
prcptnLastDspsQnt: toGridNumber(row.prcptnLastDspsQnt),
|
||||
prpwLastDspsQnt: toGridNumber(row.prpwLastDspsQnt),
|
||||
ctmnLastDspsQnt: toGridNumber(row.ctmnLastDspsQnt),
|
||||
prcptnLastDspsMthdExpln: nullableTrimmedString(row.prcptnLastDspsMthdExpln),
|
||||
ctmnLastDspsMthdExpln: nullableTrimmedString(row.ctmnLastDspsMthdExpln),
|
||||
prpwLastDspsExpln: nullableTrimmedString(row.prpwLastDspsExpln),
|
||||
ctmnLastDspsExpln: nullableTrimmedString(row.ctmnLastDspsExpln),
|
||||
oprtngDayCnt: toGridNumber(row.oprtngDayCnt),
|
||||
grssMltvQnt: toGridNumber(row.grssMltvQnt),
|
||||
wholQnt: toGridNumber(row.wholQnt),
|
||||
rmrk: nullableTrimmedString(row.rmrk),
|
||||
};
|
||||
}
|
||||
@@ -427,9 +427,9 @@ function mapRainfallPumpStationListToGridRows(
|
||||
...item,
|
||||
ctpvNm,
|
||||
oprtngStrtYmd: formatCompactYmdForGrid(item.oprtngStrtYmd),
|
||||
rsvrYn: normalizeOxYnValue(item.rsvrYn),
|
||||
runwtrYn: normalizeOxYnValue(item.runwtrYn),
|
||||
prcsgFcltYn: normalizeOxYnValue(item.prcsgFcltYn),
|
||||
ddrztnEqpmYn: normalizeOxYnValue(item.ddrztnEqpmYn),
|
||||
odorEqpmYn: normalizeOxYnValue(item.odorEqpmYn),
|
||||
oslfEtgtYn: normalizeOxYnValue(item.oslfEtgtYn),
|
||||
},
|
||||
])[0] as GridRowWithVerification;
|
||||
@@ -531,7 +531,7 @@ const SewageReservoirPumpInp = () => {
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
mdfrId: userInfo.userUnqId,
|
||||
mdfrUuid: userInfo.userUnqId,
|
||||
statsCd: statsCdForTab,
|
||||
};
|
||||
|
||||
@@ -720,13 +720,13 @@ const SewageReservoirPumpInp = () => {
|
||||
const response = isReservoirTab
|
||||
? await statisticsInputSewageReservoirPumpApi.saveSewageReservoir({
|
||||
...requestBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
saveList: saveList as SewageReservoirSaveItem[],
|
||||
deleteList: deleteList as SewageReservoirSaveItem[],
|
||||
})
|
||||
: await statisticsInputSewageReservoirPumpApi.saveRainfallPumpStation({
|
||||
...requestBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
saveList: saveList as RainfallPumpStationSaveItem[],
|
||||
deleteList: deleteList as RainfallPumpStationSaveItem[],
|
||||
});
|
||||
@@ -813,14 +813,14 @@ const SewageReservoirPumpInp = () => {
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
...(noApplicable ? { noApplicable: true } : {}),
|
||||
laePicNm: submitInfo.laePicNm,
|
||||
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm,
|
||||
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm,
|
||||
laePicTelNo: submitInfo.laePicTelNo,
|
||||
laeIdfrNm: submitInfo.laeIdfrNm,
|
||||
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
|
||||
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
|
||||
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
|
||||
lclgvPicNm: submitInfo.lclgvPicNm,
|
||||
lclgvKbrdrJbgdNm: submitInfo.lclgvKbrdrJbgdNm,
|
||||
lclgvKbrdrDeptNm: submitInfo.lclgvKbrdrDeptNm,
|
||||
lclgvPicTelnoCn: submitInfo.lclgvPicTelnoCn,
|
||||
lclgvIdfrNm: submitInfo.lclgvIdfrNm,
|
||||
lclgvIdfrJbgdNm: submitInfo.lclgvIdfrJbgdNm,
|
||||
lclgvIdfrDeptNm: submitInfo.lclgvIdfrDeptNm,
|
||||
lclgvIdfrTelnoCn: submitInfo.lclgvIdfrTelnoCn,
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -830,13 +830,13 @@ const SewageReservoirPumpInp = () => {
|
||||
const response = isReservoirTab
|
||||
? await statisticsInputSewageReservoirPumpApi.submitSewageReservoir({
|
||||
...submitRequestBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: submitList as SewageReservoirSaveItem[],
|
||||
deleteList: deleteList as SewageReservoirSaveItem[],
|
||||
})
|
||||
: await statisticsInputSewageReservoirPumpApi.submitRainfallPumpStation({
|
||||
...submitRequestBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: submitList as RainfallPumpStationSaveItem[],
|
||||
deleteList: deleteList as RainfallPumpStationSaveItem[],
|
||||
});
|
||||
@@ -858,7 +858,7 @@ const SewageReservoirPumpInp = () => {
|
||||
stats_cd: statsCdForTab,
|
||||
stats_yr: lastSearchedValues.year ?? "",
|
||||
lae_sbmsn_stts_cd:
|
||||
activeTabStatus?.laeSbmsnSttsCd === "4" ? "4" : "2",
|
||||
activeTabStatus?.lclgvSbmsnSttsCd === "4" ? "4" : "2",
|
||||
gen_verify_stts_cd: nvpassed ? "1" : "2",
|
||||
hb_pass_yn: response.data?.isHbPassed,
|
||||
};
|
||||
@@ -1525,7 +1525,7 @@ const SewageReservoirPumpInp = () => {
|
||||
? "일반검증 - "
|
||||
: "모집단 검증 -";
|
||||
return `${prefix}${e.rsnExpln}
|
||||
(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`;
|
||||
(검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
@@ -1584,7 +1584,7 @@ const SewageReservoirPumpInp = () => {
|
||||
{submitOk && <SubmitButton submitFunc={submitFunc} />}
|
||||
</>
|
||||
)}
|
||||
{activeTabStatus?.laeSbmsnSttsCd === "2" && submitOk && (
|
||||
{activeTabStatus?.lclgvSbmsnSttsCd === "2" && submitOk && (
|
||||
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
|
||||
)}
|
||||
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
|
||||
|
||||
@@ -118,8 +118,8 @@ const computeTabEnterable = (
|
||||
statusInfo: SewerageStatusInfo | null | undefined,
|
||||
periodEnterable: boolean,
|
||||
): boolean => {
|
||||
if (statusInfo?.laeSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.laeSbmsnSttsCd)) {
|
||||
if (statusInfo?.lclgvSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.lclgvSbmsnSttsCd)) {
|
||||
return false;
|
||||
}
|
||||
return periodEnterable;
|
||||
@@ -147,7 +147,7 @@ function computeTabToolbarSnapshot(
|
||||
): TabToolbarSnapshot {
|
||||
const gugunOk = buildGugunOkForToolbar(searchContext);
|
||||
const enterable = computeTabEnterable(statusInfo, periodEnterable);
|
||||
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2";
|
||||
const isSubmitted = statusInfo?.lclgvSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
|
||||
const showEnterableToolbarButtons = enterable && gugunOk;
|
||||
const showSubmittedInInputPeriodToolbar = isSubmitted && periodEnterable && gugunOk;
|
||||
@@ -215,34 +215,33 @@ function buildSewerageFinanceSettleSaveList(
|
||||
|
||||
return currentData.map((d) => ({
|
||||
admdstCd: nullableTrimmedString(d.admdstCd),
|
||||
sggNm: nullableTrimmedString(d.sggNm),
|
||||
admdstNm: nullableTrimmedString(d.admdstNm),
|
||||
cnterGovSprtAmt: formatNumber(d.cnterGovSprtAmt, "cnterGovSprtAmt"),
|
||||
soaGovSprtAmt: formatNumber(d.soaGovSprtAmt, "soaGovSprtAmt"),
|
||||
mctpvLcexpAmt: formatNumber(d.mctpvLcexpAmt, "mctpvLcexpAmt"),
|
||||
wareaSggLclAmt: formatNumber(d.wareaSggLclAmt, "wareaSggLclAmt"),
|
||||
sewerUseAmt: formatNumber(d.sewerUseAmt, "sewerUseAmt"),
|
||||
orgprBrdnAmt: formatNumber(d.orgprBrdnAmt, "orgprBrdnAmt"),
|
||||
fnnOrgprBrdnAmt: formatNumber(d.fnnOrgprBrdnAmt, "fnnOrgprBrdnAmt"),
|
||||
etcIncmAmt: formatNumber(d.etcIncmAmt, "etcIncmAmt"),
|
||||
crfwAmt: formatNumber(d.crfwAmt, "crfwAmt"),
|
||||
lbltAmt: formatNumber(d.lbltAmt, "lbltAmt"),
|
||||
soaCrfwAmt: formatNumber(d.soaCrfwAmt, "soaCrfwAmt"),
|
||||
soaLbltAmt: formatNumber(d.soaLbltAmt, "soaLbltAmt"),
|
||||
tmplntFcltAmt: formatNumber(d.tmplntFcltAmt, "tmplntFcltAmt"),
|
||||
tmplntMendngAmt: formatNumber(d.tmplntMendngAmt, "tmplntMendngAmt"),
|
||||
tmplntAmt: formatNumber(d.tmplntAmt, "tmplntAmt"),
|
||||
tmplntOperKeepAmt: formatNumber(d.tmplntOperKeepAmt, "tmplntOperKeepAmt"),
|
||||
tmplntEtcAmt: formatNumber(d.tmplntEtcAmt, "tmplntEtcAmt"),
|
||||
swagePplFcltAmt: formatNumber(d.swagePplFcltAmt, "swagePplFcltAmt"),
|
||||
swagePplMendngAmt: formatNumber(d.swagePplMendngAmt, "swagePplMendngAmt"),
|
||||
swagePplAmt: formatNumber(d.swagePplAmt, "swagePplAmt"),
|
||||
swagePplOperKeepAmt: formatNumber(d.swagePplOperKeepAmt, "swagePplOperKeepAmt"),
|
||||
swagePplEtcAmt: formatNumber(d.swagePplEtcAmt, "swagePplEtcAmt"),
|
||||
swageImpdFcltAmt: formatNumber(d.swageImpdFcltAmt, "swageImpdFcltAmt"),
|
||||
swageImpdMendngAmt: formatNumber(d.swageImpdMendngAmt, "swageImpdMendngAmt"),
|
||||
swageImpdAmt: formatNumber(d.swageImpdAmt, "swageImpdAmt"),
|
||||
swageImpdOperKeepAmt: formatNumber(d.swageImpdOperKeepAmt, "swageImpdOperKeepAmt"),
|
||||
swageImpdFcltEtcAmt: formatNumber(d.swageImpdFcltEtcAmt, "swageImpdFcltEtcAmt"),
|
||||
etcAcdntCrfwAmt: formatNumber(d.etcAcdntCrfwAmt, "etcAcdntCrfwAmt"),
|
||||
etcNrcpAmt: formatNumber(d.etcNrcpAmt, "etcNrcpAmt"),
|
||||
etcAmt: formatNumber(d.etcAmt, "etcAmt"),
|
||||
fnnEtcAmt: formatNumber(d.fnnEtcAmt, "fnnEtcAmt"),
|
||||
rmrk: nullableTrimmedString(d.rmrk),
|
||||
fnnByWholPjtcoAmt: formatNumber(d.fnnByWholPjtcoAmt, "fnnByWholPjtcoAmt"),
|
||||
usgByWholPjtcoAmt: formatNumber(d.usgByWholPjtcoAmt, "usgByWholPjtcoAmt"),
|
||||
fnnSeWholPjtcoAmt: formatNumber(d.fnnSeWholPjtcoAmt, "fnnSeWholPjtcoAmt"),
|
||||
usgSeWholPjtcoAmt: formatNumber(d.usgSeWholPjtcoAmt, "usgSeWholPjtcoAmt"),
|
||||
tmplntWholAmt: formatNumber(d.tmplntWholAmt, "tmplntWholAmt"),
|
||||
wholSwagePplAmt: formatNumber(d.wholSwagePplAmt, "wholSwagePplAmt"),
|
||||
wholSwageImpdFcltAmt: formatNumber(d.wholSwageImpdFcltAmt, "wholSwageImpdFcltAmt"),
|
||||
@@ -257,15 +256,14 @@ function buildSewerageChargeSaveList(
|
||||
|
||||
return currentData.map((d) => ({
|
||||
admdstCd: nullableTrimmedString(d.admdstCd),
|
||||
sggNm: nullableTrimmedString(d.sggNm),
|
||||
admdstNm: nullableTrimmedString(d.admdstNm),
|
||||
anlLevyQnt: formatNumber(d.anlLevyQnt, "anlLevyQnt"),
|
||||
levyAmt: formatNumber(d.levyAmt, "levyAmt"),
|
||||
grssPrmcoAmt: formatNumber(d.grssPrmcoAmt, "grssPrmcoAmt"),
|
||||
sewerLevyAmt: formatNumber(d.sewerLevyAmt, "sewerLevyAmt"),
|
||||
wholPrmcoAmt: formatNumber(d.wholPrmcoAmt, "wholPrmcoAmt"),
|
||||
rmrk: nullableTrimmedString(d.rmrk),
|
||||
avgUntprcAmt: formatNumber(d.avgUntprcAmt, "avgUntprcAmt"),
|
||||
grssUnitPrmcoAmt: formatNumber(d.grssUnitPrmcoAmt, "grssUnitPrmcoAmt"),
|
||||
rlztnRt: formatNumber(d.rlztnRt, "rlztnRt"),
|
||||
wholUnitPrmcoAmt: formatNumber(d.wholUnitPrmcoAmt, "wholUnitPrmcoAmt"),
|
||||
sewerCrgRlztnRt: formatNumber(d.sewerCrgRlztnRt, "sewerCrgRlztnRt"),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -276,32 +274,31 @@ function buildSewerageUsageFeeSaveList(
|
||||
|
||||
return currentData.map((d) => ({
|
||||
admdstCd: nullableTrimmedString(d.admdstCd),
|
||||
sggNm: nullableTrimmedString(d.sggNm),
|
||||
admdstNm: nullableTrimmedString(d.admdstNm),
|
||||
hshusLevyAmt: formatNumber(d.hshusLevyAmt, "hshusLevyAmt"),
|
||||
hshusLevyQnt: formatNumber(d.hshusLevyQnt, "hshusLevyQnt"),
|
||||
hshusUntprcAmt: formatNumber(d.hshusUntprcAmt, "hshusUntprcAmt"),
|
||||
taskFrLevyAmt: formatNumber(d.taskFrLevyAmt, "taskFrLevyAmt"),
|
||||
taskFrLevyQnt: formatNumber(d.taskFrLevyQnt, "taskFrLevyQnt"),
|
||||
taskFrUntprcAmt: formatNumber(d.taskFrUntprcAmt, "taskFrUntprcAmt"),
|
||||
ofuseLevyAmt: formatNumber(d.ofuseLevyAmt, "ofuseLevyAmt"),
|
||||
ofuseLevyQnt: formatNumber(d.ofuseLevyQnt, "ofuseLevyQnt"),
|
||||
ofuseUntprcAmt: formatNumber(d.ofuseUntprcAmt, "ofuseUntprcAmt"),
|
||||
fbuLevyAmt: formatNumber(d.fbuLevyAmt, "fbuLevyAmt"),
|
||||
fbuLevyQnt: formatNumber(d.fbuLevyQnt, "fbuLevyQnt"),
|
||||
fbuUntprcAmt: formatNumber(d.fbuUntprcAmt, "fbuUntprcAmt"),
|
||||
bthhsFrSpcs1LevyAmt: formatNumber(d.bthhsFrSpcs1LevyAmt, "bthhsFrSpcs1LevyAmt"),
|
||||
bthhsFrSpcs1LevyQnt: formatNumber(d.bthhsFrSpcs1LevyQnt, "bthhsFrSpcs1LevyQnt"),
|
||||
bthhsFrSpcs1UntprcAmt: formatNumber(d.bthhsFrSpcs1UntprcAmt, "bthhsFrSpcs1UntprcAmt"),
|
||||
bthhsFrSpcs2LevyAmt: formatNumber(d.bthhsFrSpcs2LevyAmt, "bthhsFrSpcs2LevyAmt"),
|
||||
bthhsFrSpcs2LevyQnt: formatNumber(d.bthhsFrSpcs2LevyQnt, "bthhsFrSpcs2LevyQnt"),
|
||||
bthhsFrSpcs2UntprcAmt: formatNumber(d.bthhsFrSpcs2UntprcAmt, "bthhsFrSpcs2UntprcAmt"),
|
||||
induLevyAmt: formatNumber(d.induLevyAmt, "induLevyAmt"),
|
||||
induLevyQnt: formatNumber(d.induLevyQnt, "induLevyQnt"),
|
||||
induUntprcAmt: formatNumber(d.induUntprcAmt, "induUntprcAmt"),
|
||||
btht1LevyAmt: formatNumber(d.btht1LevyAmt, "btht1LevyAmt"),
|
||||
btht1LevyQnt: formatNumber(d.btht1LevyQnt, "btht1LevyQnt"),
|
||||
btht1UntprcAmt: formatNumber(d.btht1UntprcAmt, "btht1UntprcAmt"),
|
||||
btht2LevyAmt: formatNumber(d.btht2LevyAmt, "btht2LevyAmt"),
|
||||
btht2LevyQnt: formatNumber(d.btht2LevyQnt, "btht2LevyQnt"),
|
||||
btht2UntprcAmt: formatNumber(d.btht2UntprcAmt, "btht2UntprcAmt"),
|
||||
indstLevyAmt: formatNumber(d.indstLevyAmt, "indstLevyAmt"),
|
||||
indstLevyQnt: formatNumber(d.indstLevyQnt, "indstLevyQnt"),
|
||||
indstUntprcAmt: formatNumber(d.indstUntprcAmt, "indstUntprcAmt"),
|
||||
etcLevyAmt: formatNumber(d.etcLevyAmt, "etcLevyAmt"),
|
||||
etcLevyQnt: formatNumber(d.etcLevyQnt, "etcLevyQnt"),
|
||||
etcLevyUntprcAmt: formatNumber(d.etcLevyUntprcAmt, "etcLevyUntprcAmt"),
|
||||
rmrk: nullableTrimmedString(d.rmrk),
|
||||
levyAmt: formatNumber(d.levyAmt, "levyAmt"),
|
||||
levyQnt: formatNumber(d.levyQnt, "levyQnt"),
|
||||
sewerLevyAmt: formatNumber(d.sewerLevyAmt, "sewerLevyAmt"),
|
||||
sewerLevyQnt: formatNumber(d.sewerLevyQnt, "sewerLevyQnt"),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -312,16 +309,15 @@ function buildCauserBurdenSaveList(
|
||||
|
||||
return currentData.map((d) => ({
|
||||
admdstCd: nullableTrimmedString(d.admdstCd),
|
||||
sggNm: nullableTrimmedString(d.sggNm),
|
||||
admdstNm: nullableTrimmedString(d.admdstNm),
|
||||
levyEvntcsCnt: formatNumber(d.levyEvntcsCnt, "levyEvntcsCnt"),
|
||||
levyAmt: formatNumber(d.levyAmt, "levyAmt"),
|
||||
mltvEqpmEvntcsCnt: formatNumber(d.mltvEqpmEvntcsCnt, "mltvEqpmEvntcsCnt"),
|
||||
mltvEqpmAmt: formatNumber(d.mltvEqpmAmt, "mltvEqpmAmt"),
|
||||
brdnAmtLevyNocs: formatNumber(d.brdnAmtLevyNocs, "brdnAmtLevyNocs"),
|
||||
brdnAmtLevyAmt: formatNumber(d.brdnAmtLevyAmt, "brdnAmtLevyAmt"),
|
||||
drgNocs: formatNumber(d.drgNocs, "drgNocs"),
|
||||
drgAmt: formatNumber(d.drgAmt, "drgAmt"),
|
||||
othrCstrnOthrActrCnt: formatNumber(d.othrCstrnOthrActrCnt, "othrCstrnOthrActrCnt"),
|
||||
othrCstrnOthrActrAmt: formatNumber(d.othrCstrnOthrActrAmt, "othrCstrnOthrActrAmt"),
|
||||
rmrk: nullableTrimmedString(d.rmrk),
|
||||
trgtByWholClctnAmt: formatNumber(d.trgtByWholClctnAmt, "trgtByWholClctnAmt"),
|
||||
trgtSeWholClctnAmt: formatNumber(d.trgtSeWholClctnAmt, "trgtSeWholClctnAmt"),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -773,7 +769,7 @@ const SewerageFinanceInp = () => {
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
mdfrId: userInfo.userUnqId,
|
||||
mdfrUuid: userInfo.userUnqId,
|
||||
statsCd: statsCdForTab,
|
||||
};
|
||||
|
||||
@@ -849,20 +845,19 @@ const SewerageFinanceInp = () => {
|
||||
|
||||
const activeTabStatus = tabStatusInfoMap.get(tabKey) ?? null;
|
||||
const statsCdForTab = tabStatsCdMap.get(tabKey) ?? tabKey;
|
||||
const adminOpt = isAdminInfo ? { superAdmin: "superAdmin" as const } : {};
|
||||
|
||||
const submitInfoBase = {
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
laePicNm: submitInfo.laePicNm,
|
||||
laePicTelNo: submitInfo.laePicTelNo,
|
||||
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm,
|
||||
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm,
|
||||
laeIdfrNm: submitInfo.laeIdfrNm,
|
||||
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
|
||||
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
|
||||
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
|
||||
lclgvPicNm: submitInfo.lclgvPicNm,
|
||||
lclgvPicTelnoCn: submitInfo.lclgvPicTelnoCn,
|
||||
lclgvKbrdrDeptNm: submitInfo.lclgvKbrdrDeptNm,
|
||||
lclgvKbrdrJbgdNm: submitInfo.lclgvKbrdrJbgdNm,
|
||||
lclgvIdfrNm: submitInfo.lclgvIdfrNm,
|
||||
lclgvIdfrTelnoCn: submitInfo.lclgvIdfrTelnoCn,
|
||||
lclgvIdfrDeptNm: submitInfo.lclgvIdfrDeptNm,
|
||||
lclgvIdfrJbgdNm: submitInfo.lclgvIdfrJbgdNm,
|
||||
};
|
||||
|
||||
const verificationQuery = {
|
||||
@@ -892,9 +887,8 @@ const SewerageFinanceInp = () => {
|
||||
if (isSettleTab) {
|
||||
const param: SewerageFinanceSettleSubmitRequest = {
|
||||
...submitInfoBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: buildSewerageFinanceSettleSaveList(currentData),
|
||||
...adminOpt,
|
||||
};
|
||||
response =
|
||||
await statisticsInputSewerageFinanceApi.submitSewerageFinanceSettle(
|
||||
@@ -903,26 +897,23 @@ const SewerageFinanceInp = () => {
|
||||
} else if (isChargeTab) {
|
||||
const param: SewerageChargeSubmitRequest = {
|
||||
...submitInfoBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: buildSewerageChargeSaveList(currentData),
|
||||
...adminOpt,
|
||||
};
|
||||
response = await statisticsInputSewerageFinanceApi.submitSewerageCharge(param);
|
||||
} else if (isUsageFeeTab) {
|
||||
const param: SewerageUsageFeeSubmitRequest = {
|
||||
...submitInfoBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: buildSewerageUsageFeeSaveList(currentData),
|
||||
...adminOpt,
|
||||
};
|
||||
response =
|
||||
await statisticsInputSewerageFinanceApi.submitSewerageUsageFee(param);
|
||||
} else {
|
||||
const param: CauserBurdenSubmitRequest = {
|
||||
...submitInfoBase,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: buildCauserBurdenSaveList(currentData),
|
||||
...adminOpt,
|
||||
};
|
||||
response = await statisticsInputSewerageFinanceApi.submitCauserBurden(param);
|
||||
}
|
||||
@@ -943,7 +934,7 @@ const SewerageFinanceInp = () => {
|
||||
sewer_bzmn_cd: lastSearchedValues?.bsnmCd ?? "",
|
||||
stats_cd: statsCdForTab,
|
||||
stats_yr: lastSearchedValues.year ?? "",
|
||||
lae_sbmsn_stts_cd: activeTabStatus?.laeSbmsnSttsCd === "4" ? "4" : "2",
|
||||
lae_sbmsn_stts_cd: activeTabStatus?.lclgvSbmsnSttsCd === "4" ? "4" : "2",
|
||||
gen_verify_stts_cd: nvpassed ? "1" : "2",
|
||||
hb_pass_yn: response.data?.isHbPassed,
|
||||
};
|
||||
@@ -1039,7 +1030,7 @@ const SewerageFinanceInp = () => {
|
||||
if (isSettleTab) {
|
||||
const param: SewerageFinanceSettleSaveRequest = {
|
||||
...baseRequest,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
saveList: buildSewerageFinanceSettleSaveList(currentData),
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
||||
};
|
||||
@@ -1063,7 +1054,7 @@ const SewerageFinanceInp = () => {
|
||||
if (isChargeTab) {
|
||||
const param: SewerageChargeSaveRequest = {
|
||||
...baseRequest,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
saveList: buildSewerageChargeSaveList(currentData),
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
||||
};
|
||||
@@ -1087,7 +1078,7 @@ const SewerageFinanceInp = () => {
|
||||
if (isUsageFeeTab) {
|
||||
const param: SewerageUsageFeeSaveRequest = {
|
||||
...baseRequest,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
saveList: buildSewerageUsageFeeSaveList(currentData),
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
||||
};
|
||||
@@ -1110,7 +1101,7 @@ const SewerageFinanceInp = () => {
|
||||
|
||||
const param: CauserBurdenSaveRequest = {
|
||||
...baseRequest,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
saveList: buildCauserBurdenSaveList(currentData),
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
||||
};
|
||||
@@ -1232,7 +1223,7 @@ const SewerageFinanceInp = () => {
|
||||
: e.vrfcCd === "NV"
|
||||
? "일반검증 - "
|
||||
: "모집단 검증 - ";
|
||||
return `${prefix}${e.rsnExpln}(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`;
|
||||
return `${prefix}${e.rsnExpln}(검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
@@ -1278,7 +1269,7 @@ const SewerageFinanceInp = () => {
|
||||
{submitOk && <SubmitButton submitFunc={submitFunc} />}
|
||||
</>
|
||||
)}
|
||||
{activeTabStatus?.laeSbmsnSttsCd === "2" && submitOk && (
|
||||
{activeTabStatus?.lclgvSbmsnSttsCd === "2" && submitOk && (
|
||||
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
|
||||
)}
|
||||
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
|
||||
|
||||
@@ -325,8 +325,8 @@ const SeweragePenetrationRateInp = () => {
|
||||
);
|
||||
setStatsCd(commonInfo.statsCd);
|
||||
setIsVerificationFailed(isVerificationFailedFromCommonInfo(commonInfo));
|
||||
if (statusInfo && statusInfo.laeSbmsnSttsCd) {
|
||||
if (['2', '3'].includes(statusInfo.laeSbmsnSttsCd)) {
|
||||
if (statusInfo && statusInfo.lclgvSbmsnSttsCd) {
|
||||
if (['2', '3'].includes(statusInfo.lclgvSbmsnSttsCd)) {
|
||||
setIsEnterable(false);
|
||||
} else {
|
||||
setIsEnterable(isEnterable);
|
||||
@@ -395,14 +395,10 @@ const SeweragePenetrationRateInp = () => {
|
||||
...toInputSearchRegion(sidoListRef.current, searchValues),
|
||||
ctpvNm: searchValues?.sido ?? '',
|
||||
statsYr: searchValues?.year ?? '',
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
statsCd: statsCd,
|
||||
};
|
||||
|
||||
if (isAdminInfo) {
|
||||
params.superAdmin = 'superAdmin';
|
||||
}
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
@@ -498,33 +494,32 @@ const SeweragePenetrationRateInp = () => {
|
||||
};
|
||||
|
||||
const convertedData: SewerageSupplyRateSaveItem[] = currentData.map((d) => ({
|
||||
sggNm: nullableTrimmedString(d.sggNm),
|
||||
admdstCd: nullableTrimmedString(d.admdstCd),
|
||||
admdstNm: nullableTrimmedString(d.admdstNm),
|
||||
trbtrNm: nullableTrimmedString(d.trbtrNm),
|
||||
statsTrbtrNm: nullableTrimmedString(d.statsTrbtrNm),
|
||||
spcCtpnRgnCn:
|
||||
d.spcCtpnRgnCn === '해당없음' ? null : nullableTrimmedString(d.spcCtpnRgnCn),
|
||||
ts1SwtrmtPpltnCnt: formatNumber(d.ts1SwtrmtPpltnCnt, 'ts1SwtrmtPpltnCnt'),
|
||||
ts2SwtrmtPpltnCnt: formatNumber(d.ts2SwtrmtPpltnCnt, 'ts2SwtrmtPpltnCnt'),
|
||||
ts3SwtrmtPpltnCnt: formatNumber(d.ts3SwtrmtPpltnCnt, 'ts3SwtrmtPpltnCnt'),
|
||||
ts1WwtPrcsPpltnCnt: formatNumber(d.ts1WwtPrcsPpltnCnt, 'ts1WwtPrcsPpltnCnt'),
|
||||
ts2WwtPrcsPpltnCnt: formatNumber(d.ts2WwtPrcsPpltnCnt, 'ts2WwtPrcsPpltnCnt'),
|
||||
ts3WwtPrcsPpltnCnt: formatNumber(d.ts3WwtPrcsPpltnCnt, 'ts3WwtPrcsPpltnCnt'),
|
||||
yetCntnPpltnCnt: formatNumber(d.yetCntnPpltnCnt, 'yetCntnPpltnCnt'),
|
||||
staaArea: formatNumber(d.staaArea, 'staaArea'),
|
||||
physSwtrmtPpltnCnt: formatNumber(d.physSwtrmtPpltnCnt, 'physSwtrmtPpltnCnt'),
|
||||
crtureSwtrmtPpltnCnt: formatNumber(d.crtureSwtrmtPpltnCnt, 'crtureSwtrmtPpltnCnt'),
|
||||
altdSwtrmtPpltnCnt: formatNumber(d.altdSwtrmtPpltnCnt, 'altdSwtrmtPpltnCnt'),
|
||||
physWswtPpltnCnt: formatNumber(d.physWswtPpltnCnt, 'physWswtPpltnCnt'),
|
||||
crtureWswtPpltnCnt: formatNumber(d.crtureWswtPpltnCnt, 'crtureWswtPpltnCnt'),
|
||||
altdWswtPpltnCnt: formatNumber(d.altdWswtPpltnCnt, 'altdWswtPpltnCnt'),
|
||||
urftPpltnCnt: formatNumber(d.urftPpltnCnt, 'urftPpltnCnt'),
|
||||
swtrmtZoneIntnar: formatNumber(d.swtrmtZoneIntnar, 'swtrmtZoneIntnar'),
|
||||
unprcsPpltnCnt: formatNumber(d.unprcsPpltnCnt, 'unprcsPpltnCnt'),
|
||||
drtwPrcsPpltnCnt: formatNumber(d.drtwPrcsPpltnCnt, 'drtwPrcsPpltnCnt'),
|
||||
swgdplPpltnCnt: formatNumber(d.swgdplPpltnCnt, 'swgdplPpltnCnt'),
|
||||
spctnkPpltnCnt: formatNumber(d.spctnkPpltnCnt, 'spctnkPpltnCnt'),
|
||||
staaExcpArea: formatNumber(d.staaExcpArea, 'staaExcpArea'),
|
||||
swtrmtZoneExtnar: formatNumber(d.swtrmtZoneExtnar, 'swtrmtZoneExtnar'),
|
||||
rmrk: nullableTrimmedString(d.rmrk),
|
||||
wholPpltnCnt: formatNumber(d.wholPpltnCnt, 'wholPpltnCnt'),
|
||||
wholArea: formatNumber(d.wholArea, 'wholArea'),
|
||||
whlar: formatNumber(d.whlar, 'whlar'),
|
||||
wholSwtrmtPpltnCnt: formatNumber(d.wholSwtrmtPpltnCnt, 'wholSwtrmtPpltnCnt'),
|
||||
wholPblSwtrmtPpltnCnt: formatNumber(d.wholPblSwtrmtPpltnCnt, 'wholPblSwtrmtPpltnCnt'),
|
||||
wholWwtPrcsPpltnCnt: formatNumber(d.wholWwtPrcsPpltnCnt, 'wholWwtPrcsPpltnCnt'),
|
||||
wholStaaExcpPpltnCnt: formatNumber(d.wholStaaExcpPpltnCnt, 'wholStaaExcpPpltnCnt'),
|
||||
staaPsplyRt: formatNumber(d.staaPsplyRt, 'staaPsplyRt'),
|
||||
altdPrcsPsplyRt: formatNumber(d.altdPrcsPsplyRt, 'altdPrcsPsplyRt'),
|
||||
wholWswtPpltnCnt: formatNumber(d.wholWswtPpltnCnt, 'wholWswtPpltnCnt'),
|
||||
wholStaaOtsdPpltnCnt: formatNumber(d.wholStaaOtsdPpltnCnt, 'wholStaaOtsdPpltnCnt'),
|
||||
swagePplPsplyRt: formatNumber(d.swagePplPsplyRt, 'swagePplPsplyRt'),
|
||||
awtrPsplyRt: formatNumber(d.awtrPsplyRt, 'awtrPsplyRt'),
|
||||
sewerInstlRt: formatNumber(d.sewerInstlRt, 'sewerInstlRt'),
|
||||
}));
|
||||
|
||||
@@ -532,17 +527,17 @@ const SeweragePenetrationRateInp = () => {
|
||||
...toInputSearchRegion(sidoListRef.current, searchValues),
|
||||
ctpvNm: searchValues?.sido ?? '',
|
||||
statsYr: searchValues?.year ?? '',
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: convertedData,
|
||||
sewerBzmnArea: footerFromApi?.sewerBzmnArea,
|
||||
laePicNm: submitInfo.laePicNm,
|
||||
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm,
|
||||
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm,
|
||||
laePicTelNo: submitInfo.laePicTelNo,
|
||||
laeIdfrNm: submitInfo.laeIdfrNm,
|
||||
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
|
||||
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
|
||||
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
|
||||
lclgvPicNm: submitInfo.lclgvPicNm,
|
||||
lclgvKbrdrJbgdNm: submitInfo.lclgvKbrdrJbgdNm,
|
||||
lclgvKbrdrDeptNm: submitInfo.lclgvKbrdrDeptNm,
|
||||
lclgvPicTelnoCn: submitInfo.lclgvPicTelnoCn,
|
||||
lclgvIdfrNm: submitInfo.lclgvIdfrNm,
|
||||
lclgvIdfrJbgdNm: submitInfo.lclgvIdfrJbgdNm,
|
||||
lclgvIdfrDeptNm: submitInfo.lclgvIdfrDeptNm,
|
||||
lclgvIdfrTelnoCn: submitInfo.lclgvIdfrTelnoCn,
|
||||
};
|
||||
|
||||
setGlobalProgressLabel('일반 검증 진행 중...');
|
||||
@@ -557,7 +552,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
sewer_bzmn_cd: searchValues?.bsnmCd ?? '',
|
||||
stats_cd: statsCd,
|
||||
stats_yr: searchValues?.year ?? '',
|
||||
lae_sbmsn_stts_cd: (statusInfo?.laeSbmsnSttsCd === '4' ? '4' : '2'),
|
||||
lae_sbmsn_stts_cd: (statusInfo?.lclgvSbmsnSttsCd === '4' ? '4' : '2'),
|
||||
gen_verify_stts_cd: response.data.nvpassed ? '1' : '2',
|
||||
hb_pass_yn: response.data.isHbPassed,
|
||||
};
|
||||
@@ -677,41 +672,39 @@ const SeweragePenetrationRateInp = () => {
|
||||
};
|
||||
|
||||
const convertedData: SewerageSupplyRateSaveItem[] = currentData.map((d) => ({
|
||||
// 행정구역
|
||||
sggNm: nullableTrimmedString(d.sggNm), // 시군구
|
||||
admdstCd: nullableTrimmedString(d.admdstCd), // 행정코드
|
||||
admdstNm: nullableTrimmedString(d.admdstNm), // 행정구역명
|
||||
trbtrNm: nullableTrimmedString(d.trbtrNm), // 지류
|
||||
statsTrbtrNm: nullableTrimmedString(d.statsTrbtrNm), // 지류
|
||||
spcCtpnRgnCn:
|
||||
d.spcCtpnRgnCn === '해당없음'
|
||||
? null
|
||||
: nullableTrimmedString(d.spcCtpnRgnCn), // 특별대책지역
|
||||
// 공공하수처리인구(명) - 물리적/생물학적/고도처리
|
||||
ts1SwtrmtPpltnCnt: formatNumber(d.ts1SwtrmtPpltnCnt, 'ts1SwtrmtPpltnCnt'), // 물리적처리
|
||||
ts2SwtrmtPpltnCnt: formatNumber(d.ts2SwtrmtPpltnCnt, 'ts2SwtrmtPpltnCnt'), // 생물학적처리
|
||||
ts3SwtrmtPpltnCnt: formatNumber(d.ts3SwtrmtPpltnCnt, 'ts3SwtrmtPpltnCnt'), // 고도처리
|
||||
physSwtrmtPpltnCnt: formatNumber(d.physSwtrmtPpltnCnt, 'physSwtrmtPpltnCnt'), // 물리적처리
|
||||
crtureSwtrmtPpltnCnt: formatNumber(d.crtureSwtrmtPpltnCnt, 'crtureSwtrmtPpltnCnt'), // 생물학적처리
|
||||
altdSwtrmtPpltnCnt: formatNumber(d.altdSwtrmtPpltnCnt, 'altdSwtrmtPpltnCnt'), // 고도처리
|
||||
// 폐수처리인구(명) - 물리적/생물학적/고도처리
|
||||
ts1WwtPrcsPpltnCnt: formatNumber(d.ts1WwtPrcsPpltnCnt, 'ts1WwtPrcsPpltnCnt'),
|
||||
ts2WwtPrcsPpltnCnt: formatNumber(d.ts2WwtPrcsPpltnCnt, 'ts2WwtPrcsPpltnCnt'),
|
||||
ts3WwtPrcsPpltnCnt: formatNumber(d.ts3WwtPrcsPpltnCnt, 'ts3WwtPrcsPpltnCnt'),
|
||||
yetCntnPpltnCnt: formatNumber(d.yetCntnPpltnCnt, 'yetCntnPpltnCnt'), // 미접속인구
|
||||
staaArea: formatNumber(d.staaArea, 'staaArea'), // 하수처리구역 내 면적(㎢)
|
||||
physWswtPpltnCnt: formatNumber(d.physWswtPpltnCnt, 'physWswtPpltnCnt'),
|
||||
crtureWswtPpltnCnt: formatNumber(d.crtureWswtPpltnCnt, 'crtureWswtPpltnCnt'),
|
||||
altdWswtPpltnCnt: formatNumber(d.altdWswtPpltnCnt, 'altdWswtPpltnCnt'),
|
||||
urftPpltnCnt: formatNumber(d.urftPpltnCnt, 'urftPpltnCnt'), // 미접속인구
|
||||
swtrmtZoneIntnar: formatNumber(d.swtrmtZoneIntnar, 'swtrmtZoneIntnar'), // 하수처리구역 내 면적(㎢)
|
||||
// 하수처리구역 외
|
||||
unprcsPpltnCnt: formatNumber(d.unprcsPpltnCnt, 'unprcsPpltnCnt'), // 미처리인구
|
||||
drtwPrcsPpltnCnt: formatNumber(d.drtwPrcsPpltnCnt, 'drtwPrcsPpltnCnt'), // 오수처리인구
|
||||
swgdplPpltnCnt: formatNumber(d.swgdplPpltnCnt, 'swgdplPpltnCnt'), // 오수처리인구
|
||||
spctnkPpltnCnt: formatNumber(d.spctnkPpltnCnt, 'spctnkPpltnCnt'), // 정화조인구
|
||||
staaExcpArea: formatNumber(d.staaExcpArea, 'staaExcpArea'), // 하수처리구역 외 면적(㎢)
|
||||
swtrmtZoneExtnar: formatNumber(d.swtrmtZoneExtnar, 'swtrmtZoneExtnar'), // 하수처리구역 외 면적(㎢)
|
||||
rmrk: nullableTrimmedString(d.rmrk), // 비고
|
||||
// 합계·소계(계산필드 저장용)
|
||||
wholPpltnCnt: formatNumber(d.wholPpltnCnt, 'wholPpltnCnt'), // 총인구(명)
|
||||
wholArea: formatNumber(d.wholArea, 'wholArea'), // 총면적(㎢)
|
||||
whlar: formatNumber(d.whlar, 'whlar'), // 총면적(㎢)
|
||||
wholSwtrmtPpltnCnt: formatNumber(d.wholSwtrmtPpltnCnt, 'wholSwtrmtPpltnCnt'), // 하수처리구역 내 합계
|
||||
wholPblSwtrmtPpltnCnt: formatNumber(d.wholPblSwtrmtPpltnCnt, 'wholPblSwtrmtPpltnCnt'), // 공공하수처리인구 소계
|
||||
wholWwtPrcsPpltnCnt: formatNumber(d.wholWwtPrcsPpltnCnt, 'wholWwtPrcsPpltnCnt'), // 폐수처리인구 소계
|
||||
wholStaaExcpPpltnCnt: formatNumber(d.wholStaaExcpPpltnCnt, 'wholStaaExcpPpltnCnt'), // 하수처리구역 외 합계
|
||||
wholWswtPpltnCnt: formatNumber(d.wholWswtPpltnCnt, 'wholWswtPpltnCnt'), // 폐수처리인구 소계
|
||||
wholStaaOtsdPpltnCnt: formatNumber(d.wholStaaOtsdPpltnCnt, 'wholStaaOtsdPpltnCnt'), // 하수처리구역 외 합계
|
||||
// 보급률(%)
|
||||
staaPsplyRt: formatNumber(d.staaPsplyRt, 'staaPsplyRt'), // 공공하수처리구역 인구보급률
|
||||
altdPrcsPsplyRt: formatNumber(d.altdPrcsPsplyRt, 'altdPrcsPsplyRt'), // 고도처리 인구보급률
|
||||
swagePplPsplyRt: formatNumber(d.swagePplPsplyRt, 'swagePplPsplyRt'), // 공공하수처리구역 인구보급률
|
||||
awtrPsplyRt: formatNumber(d.awtrPsplyRt, 'awtrPsplyRt'), // 고도처리 인구보급률
|
||||
sewerInstlRt: formatNumber(d.sewerInstlRt, 'sewerInstlRt'), // 하수도 설치율
|
||||
}));
|
||||
|
||||
@@ -719,7 +712,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
...toInputSearchRegion(sidoListRef.current, searchValues),
|
||||
ctpvNm: searchValues?.sido ?? '', // 시도
|
||||
statsYr: searchValues?.year ?? '', // 통계 연도
|
||||
userId: userInfo.userUnqId, // 사용자
|
||||
userUuid: userInfo.userUnqId, // 사용자
|
||||
saveList: convertedData, // 저장 데이터
|
||||
sewerBzmnArea: footerFromApi?.sewerBzmnArea, // 행정참고자료면적
|
||||
};
|
||||
@@ -763,7 +756,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
...toInputSearchRegion(sidoListRef.current, searchValues),
|
||||
ctpvNm: searchValues?.sido ?? '',
|
||||
statsYr: searchValues?.year ?? '',
|
||||
mdfrId: userInfo.userUnqId,
|
||||
mdfrUuid: userInfo.userUnqId,
|
||||
statsCd: statsCd,
|
||||
};
|
||||
|
||||
@@ -794,7 +787,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
const submitOk = isSubmitOkForToolbar(sidoList, toolbarSearchContext);
|
||||
|
||||
const inputPeriodCurrent = isInputPeriodCurrent(periodCheckList);
|
||||
const isSubmitted = statusInfo?.laeSbmsnSttsCd === '2';
|
||||
const isSubmitted = statusInfo?.lclgvSbmsnSttsCd === '2';
|
||||
const baseShowSaveInToolbar =
|
||||
(isEnterable || isAdminInfo) && inputPeriodCurrent && gugunOkOrNotDoList;
|
||||
const baseShowEnterableToolbarButtons = isEnterable && gugunOkOrNotDoList;
|
||||
@@ -834,7 +827,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
{submitOk && <SubmitButton submitFunc={() => submitFunc(false)} />}
|
||||
</>
|
||||
)}
|
||||
{statusInfo?.laeSbmsnSttsCd === '2' && submitOk && <CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />}
|
||||
{statusInfo?.lclgvSbmsnSttsCd === '2' && submitOk && <CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />}
|
||||
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
|
||||
</div>
|
||||
</div>
|
||||
@@ -878,7 +871,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
.map((e) => {
|
||||
const prefix = e.vrfcCd === 'IMR' ? '시계열 검증 - ' : (e.vrfcCd === 'NV' ? '일반검증 - ' : '모집단 검증 -');
|
||||
return `${prefix}${e.rsnExpln}
|
||||
(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`;
|
||||
(검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join('\n');
|
||||
|
||||
@@ -119,8 +119,8 @@ const computeTabEnterable = (
|
||||
statusInfo: SewerageStatusInfo | null | undefined,
|
||||
periodEnterable: boolean,
|
||||
): boolean => {
|
||||
if (statusInfo?.laeSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.laeSbmsnSttsCd)) {
|
||||
if (statusInfo?.lclgvSbmsnSttsCd) {
|
||||
if (["2", "3"].includes(statusInfo.lclgvSbmsnSttsCd)) {
|
||||
return false;
|
||||
}
|
||||
return periodEnterable;
|
||||
@@ -156,7 +156,7 @@ function computeTabToolbarSnapshot(
|
||||
): TabToolbarSnapshot {
|
||||
const gugunOk = buildGugunOkForToolbar(searchContext);
|
||||
const enterable = computeTabEnterable(statusInfo, periodEnterable);
|
||||
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2";
|
||||
const isSubmitted = statusInfo?.lclgvSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
|
||||
const showEnterableToolbarButtons = enterable && gugunOk;
|
||||
const showSubmittedInInputPeriodToolbar = isSubmitted && periodEnterable && gugunOk;
|
||||
@@ -223,18 +223,18 @@ function buildComplaintSaveList(
|
||||
admdstCd: nullableTrimmedString(d.admdstCd),
|
||||
admdstNm: nullableTrimmedString(d.admdstNm),
|
||||
swtrmtPpltnCnt: formatNumber(d.swtrmtPpltnCnt, "swtrmtPpltnCnt"),
|
||||
dschgWqCnt: formatNumber(d.dschgWqCnt, "dschgWqCnt"),
|
||||
odorCnt: formatNumber(d.odorCnt, "odorCnt"),
|
||||
ldsCnt: formatNumber(d.ldsCnt, "ldsCnt"),
|
||||
plltnAcdntCnt: formatNumber(d.plltnAcdntCnt, "plltnAcdntCnt"),
|
||||
swagePplCnt: formatNumber(d.swagePplCnt, "swagePplCnt"),
|
||||
sewerCrgAmt: formatNumber(d.sewerCrgAmt, "sewerCrgAmt"),
|
||||
etcCnt: formatNumber(d.etcCnt, "etcCnt"),
|
||||
cvlcptDschgWq: formatNumber(d.cvlcptDschgWq, "cvlcptDschgWq"),
|
||||
cvlcptOdorCnt: formatNumber(d.cvlcptOdorCnt, "cvlcptOdorCnt"),
|
||||
cvlcptDrgCnt: formatNumber(d.cvlcptDrgCnt, "cvlcptDrgCnt"),
|
||||
cvlcptMnhlCnt: formatNumber(d.cvlcptMnhlCnt, "cvlcptMnhlCnt"),
|
||||
cvlcptSpctnkCnt: formatNumber(d.cvlcptSpctnkCnt, "cvlcptSpctnkCnt"),
|
||||
cvlcptSewerCrg: formatNumber(d.cvlcptSewerCrg, "cvlcptSewerCrg"),
|
||||
cvlcptEtcCnt: formatNumber(d.cvlcptEtcCnt, "cvlcptEtcCnt"),
|
||||
rmrk: nullableTrimmedString(d.rmrk),
|
||||
whol: formatNumber(d.whol, "whol"),
|
||||
crtrPpltnCvlcptEvntcsCnt: formatNumber(
|
||||
d.crtrPpltnCvlcptEvntcsCnt,
|
||||
"crtrPpltnCvlcptEvntcsCnt",
|
||||
cvlcptWholCnt: formatNumber(d.cvlcptWholCnt, "cvlcptWholCnt"),
|
||||
crtrPpltnCvlcptCnt: formatNumber(
|
||||
d.crtrPpltnCvlcptCnt,
|
||||
"crtrPpltnCvlcptCnt",
|
||||
),
|
||||
}));
|
||||
}
|
||||
@@ -250,15 +250,15 @@ function buildResidentFriendlyFactSaveList(
|
||||
swtrmtFcltCd: nullableTrimmedString(d.swtrmtFcltCd),
|
||||
swtrmtFcltNm: nullableTrimmedString(d.swtrmtFcltNm),
|
||||
fcltSn: nullableTrimmedString(d.fcltSn),
|
||||
tmplntArea: formatNumber(d.tmplntArea, "tmplntArea"),
|
||||
brkFcltArea: formatNumber(d.brkFcltArea, "brkFcltArea"),
|
||||
stpar: formatNumber(d.stpar, "stpar"),
|
||||
brkFcar: formatNumber(d.brkFcar, "brkFcar"),
|
||||
childFrshpSpacArea: formatNumber(d.childFrshpSpacArea, "childFrshpSpacArea"),
|
||||
bdySportsFcltArea: formatNumber(d.bdySportsFcltArea, "bdySportsFcltArea"),
|
||||
sportsFcltArea: formatNumber(d.sportsFcltArea, "sportsFcltArea"),
|
||||
pkltArea: formatNumber(d.pkltArea, "pkltArea"),
|
||||
eduMultiFcltArea: formatNumber(d.eduMultiFcltArea, "eduMultiFcltArea"),
|
||||
bdySportsFcar: formatNumber(d.bdySportsFcar, "bdySportsFcar"),
|
||||
sportsFcar: formatNumber(d.sportsFcar, "sportsFcar"),
|
||||
pkltar: formatNumber(d.pkltar, "pkltar"),
|
||||
eduMultiFcar: formatNumber(d.eduMultiFcar, "eduMultiFcar"),
|
||||
wtlndMakeArea: formatNumber(d.wtlndMakeArea, "wtlndMakeArea"),
|
||||
etcCnt: formatNumber(d.etcCnt, "etcCnt"),
|
||||
othrar: formatNumber(d.othrar, "othrar"),
|
||||
rmrk: nullableTrimmedString(d.rmrk),
|
||||
}));
|
||||
}
|
||||
@@ -772,14 +772,14 @@ const SewerageServiceLevelInp = () => {
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
laePicNm: submitInfo.laePicNm,
|
||||
laePicTelNo: submitInfo.laePicTelNo,
|
||||
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm,
|
||||
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm,
|
||||
laeIdfrNm: submitInfo.laeIdfrNm,
|
||||
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
|
||||
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
|
||||
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
|
||||
lclgvPicNm: submitInfo.lclgvPicNm,
|
||||
lclgvPicTelnoCn: submitInfo.lclgvPicTelnoCn,
|
||||
lclgvKbrdrDeptNm: submitInfo.lclgvKbrdrDeptNm,
|
||||
lclgvKbrdrJbgdNm: submitInfo.lclgvKbrdrJbgdNm,
|
||||
lclgvIdfrNm: submitInfo.lclgvIdfrNm,
|
||||
lclgvIdfrTelnoCn: submitInfo.lclgvIdfrTelnoCn,
|
||||
lclgvIdfrDeptNm: submitInfo.lclgvIdfrDeptNm,
|
||||
lclgvIdfrJbgdNm: submitInfo.lclgvIdfrJbgdNm,
|
||||
};
|
||||
|
||||
const verificationQuery = {
|
||||
@@ -807,14 +807,12 @@ const SewerageServiceLevelInp = () => {
|
||||
const response = isComplaintTab
|
||||
? await statisticsInputSewerageServiceLevelApi.submitComplaintList({
|
||||
...submitInfoBase,
|
||||
userId: userInfo.userUnqId,
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
||||
userUuid: userInfo.userUnqId,
|
||||
submitList: complaintSubmitList,
|
||||
})
|
||||
: await statisticsInputSewerageServiceLevelApi.submitResidentFriendlyFactList({
|
||||
...submitInfoBase,
|
||||
userId: userInfo.userUnqId,
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
||||
userUuid: userInfo.userUnqId,
|
||||
noApplicable,
|
||||
submitList: residentSubmitList,
|
||||
});
|
||||
@@ -835,7 +833,7 @@ const SewerageServiceLevelInp = () => {
|
||||
sewer_bzmn_cd: lastSearchedValues?.bsnmCd ?? "",
|
||||
stats_cd: statsCdForTab,
|
||||
stats_yr: lastSearchedValues.year ?? "",
|
||||
lae_sbmsn_stts_cd: activeTabStatus?.laeSbmsnSttsCd === "4" ? "4" : "2",
|
||||
lae_sbmsn_stts_cd: activeTabStatus?.lclgvSbmsnSttsCd === "4" ? "4" : "2",
|
||||
gen_verify_stts_cd: nvpassed ? "1" : "2",
|
||||
hb_pass_yn: response.data?.isHbPassed,
|
||||
};
|
||||
@@ -941,7 +939,7 @@ const SewerageServiceLevelInp = () => {
|
||||
const response =
|
||||
await statisticsInputSewerageServiceLevelApi.saveComplaintList({
|
||||
...baseRequest,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
||||
saveList,
|
||||
});
|
||||
@@ -967,7 +965,7 @@ const SewerageServiceLevelInp = () => {
|
||||
await statisticsInputSewerageServiceLevelApi.saveResidentFriendlyFactList(
|
||||
{
|
||||
...baseRequest,
|
||||
userId: userInfo.userUnqId,
|
||||
userUuid: userInfo.userUnqId,
|
||||
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
||||
noApplicable,
|
||||
saveList,
|
||||
@@ -1021,7 +1019,7 @@ const SewerageServiceLevelInp = () => {
|
||||
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
||||
ctpvNm: lastSearchedValues.sido ?? "",
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
mdfrId: userInfo.userUnqId,
|
||||
mdfrUuid: userInfo.userUnqId,
|
||||
statsCd: statsCdForTab,
|
||||
};
|
||||
|
||||
@@ -1156,7 +1154,7 @@ const SewerageServiceLevelInp = () => {
|
||||
: e.vrfcCd === "NV"
|
||||
? "일반검증 - "
|
||||
: "모집단 검증 - ";
|
||||
return `${prefix}${e.rsnExpln}(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`;
|
||||
return `${prefix}${e.rsnExpln}(검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
@@ -1202,7 +1200,7 @@ const SewerageServiceLevelInp = () => {
|
||||
{submitOk && <SubmitButton submitFunc={submitFunc} />}
|
||||
</>
|
||||
)}
|
||||
{activeTabStatus?.laeSbmsnSttsCd === "2" && submitOk && (
|
||||
{activeTabStatus?.lclgvSbmsnSttsCd === "2" && submitOk && (
|
||||
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
|
||||
)}
|
||||
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
|
||||
|
||||
@@ -161,7 +161,7 @@ const StatsInputStatusInp = () => {
|
||||
}
|
||||
|
||||
const hasInvalidStatus = rowData.some((row) =>
|
||||
["0", "1", "4"].includes(String(row.laeSbmsnSttsCd ?? "")),
|
||||
["0", "1", "4"].includes(String(row.lclgvSbmsnSttsCd ?? "")),
|
||||
);
|
||||
|
||||
if (hasInvalidStatus) {
|
||||
@@ -176,8 +176,8 @@ const StatsInputStatusInp = () => {
|
||||
setIsApproveLoading(true);
|
||||
try {
|
||||
const userInfo = await getUserInfo();
|
||||
const mdfrId = (userInfo.userUnqId ?? "").trim();
|
||||
if (!mdfrId) {
|
||||
const mdfrUuid = (userInfo.userUnqId ?? "").trim();
|
||||
if (!mdfrUuid) {
|
||||
alert("수정자 ID를 확인할 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
@@ -189,7 +189,7 @@ const StatsInputStatusInp = () => {
|
||||
.map((row) => {
|
||||
const statsCd = String(row.statsCd ?? "").trim();
|
||||
const sewerBzmnCd = String(row.sewerBzmnCd ?? sewerBzmnCdDefault).trim();
|
||||
return { statsYr, sewerBzmnCd, statsCd, mdfrId };
|
||||
return { statsYr, sewerBzmnCd, statsCd, mdfrUuid };
|
||||
})
|
||||
.filter((item) => item.statsCd.length > 0);
|
||||
|
||||
@@ -260,7 +260,7 @@ const StatsInputStatusInp = () => {
|
||||
const allRowsLaeSbmsnApproved = useMemo(
|
||||
() =>
|
||||
rowData.length > 0 &&
|
||||
rowData.every((row) => String(row.laeSbmsnSttsCd ?? "") === "3"),
|
||||
rowData.every((row) => String(row.lclgvSbmsnSttsCd ?? "") === "3"),
|
||||
[rowData],
|
||||
);
|
||||
|
||||
@@ -301,11 +301,11 @@ const StatsInputStatusInp = () => {
|
||||
getGridRef={gridRef}
|
||||
onDataCellClick={handleExceptionSubmitHistoryCellClick}
|
||||
cellStyleCallback={({ row, fieldName }) => {
|
||||
if (fieldName !== "laeSbmsnSttsNm") {
|
||||
if (fieldName !== "lclgvSbmsnSttsNm") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const statusCd = String(row.laeSbmsnSttsCd ?? "");
|
||||
const statusCd = String(row.lclgvSbmsnSttsCd ?? "");
|
||||
if (statusCd === "0" || statusCd === "1") {
|
||||
return "rg-cell-stats-status-pending";
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user