Merge branch 'refs/heads/main' into feature/0612_sentry_fr_merge
# Conflicts: # pnpm-lock.yaml
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
>
|
||||
> - 프로세스·상태·버튼: `03.재원협의_실무협의_프로세스_통합_v완결.md`
|
||||
> - REST 설계: `05.재원협의_상태별_endpoint_설계.md`
|
||||
> - 공통코드: `FNRSSTTS` (`SV, ST, LE, FV, ER, SL, AC, TE, TP, RM, RB, RC, RA, AV, RK, RR, EP, TA`)
|
||||
> - 공통코드: `FNRSSTTS` (`SV, ST, LE, FV, ER, SL, AC, TE, TP, RM, RB, RC, RA, AV, EV, RK, RR, EP, TA`)
|
||||
>
|
||||
> **백엔드 서비스**:
|
||||
>
|
||||
@@ -40,7 +40,7 @@ v1.0은 공단 접수자의 **자료검증 → 초안생성 → 웹기안기**
|
||||
| [ref-screen-buttons.md](./ref-screen-buttons.md) | 38행 화면·역할·버튼 매트릭스, 버튼 가시성 | `button-config.ts` 개편 |
|
||||
| [ref-endpoint.md](./ref-endpoint.md) | 버튼→API 총괄, 단계별 입출력, FE 호출 금지 API, Gap Analysis | 엔티티·service 매핑 |
|
||||
| [ref-frontend-entities.md](./ref-frontend-entities.md) | ENDPOINT, 슬라이스, Service 시그니처, 폴링, 웹기안기, DTO | 엔티티 레이어 구현 |
|
||||
| [ref-webhwp-api.md](./ref-webhwp-api.md) | 웹한글기안기 v3 `HwpCtrl` Properties/Methods, Open/SaveAs | 기안기 위젯 연동 |
|
||||
| [ref-webhwp-api.md](./ref-webhwp-api.md) | 웹한글기안기 v3 `HwpCtrl` Properties/Methods, Open/SaveAs | 기안기 위젯 연동 |
|
||||
|
||||
---
|
||||
|
||||
@@ -98,7 +98,7 @@ v1.0은 공단 접수자의 **자료검증 → 초안생성 → 웹기안기**
|
||||
정방향:
|
||||
|
||||
```
|
||||
SV → ST → LE → FV → ER → AC → TE → TP → RM → RB → RC → RA → AV → RK → RR → EP → TA
|
||||
SV → ST → LE → FV → ER → AC → TE → TP → RM → RB → RC → RA → AV → EV → RK → RR → EP → TA
|
||||
```
|
||||
|
||||
자체종결: `LE → EP → TA`
|
||||
@@ -119,7 +119,8 @@ SV → ST → LE → FV → ER → AC → TE → TP → RM → RB → RC → RA
|
||||
| `RB` | 지자체회신 | 기초 실무협의서 관리 / 조치완료 |
|
||||
| `RC` | 조치결과서 제출 | 광역 → 환경청 `RA` |
|
||||
| `RA` | 조치결과서제출(환경청) | 환경청 2차 의뢰 `AV` |
|
||||
| `AV` | 금액검증 | AI 진행 표시. **30분** 후 `RK` Fallback |
|
||||
| `AV` | 금액검증 | AI 진행 표시. **30분** 후 `EV` Fallback |
|
||||
| `EV` | 조치결과검토의뢰 | 공단 접수자: 2차 결재선 검토자지정 → `RK` |
|
||||
| `RK` | 조치결과서검토 | 웹기안기·금액검증 재실행·보완. 결재선 2차 |
|
||||
| `RR` | 조치결과서검토 회신 | 환경청 → 광역 `EP` |
|
||||
| `EP` | 검토결과 회신 | 광역 → 기초 `TA` |
|
||||
@@ -144,7 +145,7 @@ SV → ST → LE → FV → ER → AC → TE → TP → RM → RB → RC → RA
|
||||
6. **진행상태만 바꾸는 버튼은** `POST /fnrs-cnsltn-mng/doc/updatePrgrsStts`**를 재사용한다.** (`ST→LE`, `RM→RB`, `RC→RA`, `RR→EP`, `EP→TA`)
|
||||
7. **보완요청/보완완료는 공통 API** `reqsupplement` / `submitsupplement`. 복귀 상태(`ER`/`TE`/`RK`)는 서버가 이력에서 결정한다. 프론트는 직전 상태를 추측해 보내지 않는다.
|
||||
8. **결재 승인/회신은** `PUT /elapr-prgrs-stts/{id}/process` **하나**. 1차 최종승인자 → `TP`, 2차 → `RR`은 서버가 Feign으로 처리한다.
|
||||
9. **2차 결재선 복사는 백엔드**. `POST /elapr-apln/copy-line`은 `AV→RK` 콜백에서 호출한다. 프론트는 `RK`에서 2차 결재선을 **조회·표시**만 한다.
|
||||
9. **2차 결재선은 EV에서 수동 검토자지정**. `POST /elapr-dmnd` (`elaprCycl: 2`, `docPrgrsSttsComCd: RK`). 1차 결재선 자동 copy-line은 사용하지 않는다.
|
||||
10. **웹기안기는 `webhwp/open` 응답을 axios `blob`으로 조회한 뒤 `File`로 감싸 `HwpCtrl.Open`에 전달한다.** `blob:` Object URL과 `orgnlOtptDocCn`은 쓰지 않는다.
|
||||
|
||||
---
|
||||
@@ -189,6 +190,7 @@ BEGIN
|
||||
ON [파일보완] (ER, ATRZ01) → GET file-vrfc-results → 팝업 → reqsupplement
|
||||
ON [파일검증] (ER, ATRZ01) → dcrag-to transfer (상태 유지)
|
||||
ON [검토자지정] (ER, ATRZ01) → POST elapr-dmnd (docPrgrsSttsComCd: AC) → START POLL AC
|
||||
ON [검토자지정] (EV, ATRZ01) → POST elapr-dmnd (elaprCycl: 2, docPrgrsSttsComCd: RK)
|
||||
ON [실무협의서관리] (TE|RK) → GET webhwp/open (blob) → HwpCtrl.Open(File)
|
||||
ON [실무협의서생성] (TE, 실패/타임아웃) → dcrag-to draft/generate
|
||||
ON [검토결과조회] (TE) → pwrk-cnsltn-mng findById | findByUpDocNo
|
||||
@@ -212,36 +214,37 @@ END
|
||||
|
||||
프론트가 치는 것만. 콜백·스케줄러는 제외.
|
||||
|
||||
| 주체 | 버튼 | From → To | Method & URI |
|
||||
| --------- | -------------------------- | ----------------------------- | --------------------------------------------------------------- | --- | ------------------------------------------------ |
|
||||
| 기초 | 저장 / 신청완료 | →`SV` / `SV→ST` | `POST /fnrs-cnsltn-mng/save-apply` |
|
||||
| 기초 | 삭제 | `SV` | `DELETE /fnrs-cnsltn-mng/{docNo}` |
|
||||
| 광역 | 환경청제출 | `ST→LE` | `POST /fnrs-cnsltn-mng/doc/updatePrgrsStts` |
|
||||
| 환경청 | 결과회신(자체종결) | `LE→EP` | `POST /fnrs-cnsltn-mng/{docNo}/tech-review` |
|
||||
| 환경청 | 기술검토 의뢰 | `LE→FV` | **신규** `POST /fnrs-cnsltn-mng/{docNo}/request-examine` |
|
||||
| 공단 접수 | 파일보완 팝업 | `ER` | `GET /fnrs-cnsltn-mng/{docNo}/file-vrfc-results` |
|
||||
| 공단 접수 | 파일보완요청 | `ER→SL` | `POST /fnrs-cnsltn-mng/{docNo}/reqsupplement` |
|
||||
| 공단 접수 | 파일검증 재실행 | `ER` 유지 | `POST /dcrag-to/documents/transfer` |
|
||||
| 공단 접수 | 검토자지정 | `ER→AC` | `POST /elapr-dmnd` |
|
||||
| 공단 검토 | 실무협의서관리 | `TE`/`RK` | `GET .../pwrk-cnsltn-doc-crt-rslt/{uuid}/webhwp/open` |
|
||||
| 공단 검토 | 실무협의서생성 | `TE` 유지 | `POST /dcrag-to/documents/draft/generate` |
|
||||
| 공단 검토 | 저장 | `TE`/`RK` | `POST /pwrk-cnsltn-mng/save-apply` |
|
||||
| 공단 | 검토완료 / 결재(승인·회신) | `TE` 유지 / `TE→TP` / `RK→RR` | `PUT /elapr-prgrs-stts/{id}/process` |
|
||||
| 공단 검토 | 보완요청 | `TE→SL` / `RK→SL` | `POST /fnrs-cnsltn-mng/{docNo}/reqsupplement` |
|
||||
| 기초 | 파일보완완료 / 보완완료 | `SL→ER | TE | RK` | `POST /fnrs-cnsltn-mng/{docNo}/submitsupplement` |
|
||||
| 환경청 | 재원협의 결과 회신 | `TP→RM` | `POST /fnrs-cnsltn-mng/{docNo}/tech-review` |
|
||||
| 광역 | 지자체회신 | `RM→RB` | `updatePrgrsStts` |
|
||||
| 기초 | 실무협의서 관리 | `RB` 유지 | `GET .../pwrk-cnsltn-doc-crt-rslt/{uuid}/webhwp/open` |
|
||||
| 주체 | 버튼 | From → To | Method & URI |
|
||||
| --------- | -------------------------- | ----------------------------- | ---------------------------------------------------------------------------------- |
|
||||
| 기초 | 저장 / 신청완료 | →`SV` / `SV→ST` | `POST /fnrs-cnsltn-mng/save-apply` |
|
||||
| 기초 | 삭제 | `SV` | `DELETE /fnrs-cnsltn-mng/{docNo}` |
|
||||
| 광역 | 환경청제출 | `ST→LE` | `POST /fnrs-cnsltn-mng/doc/updatePrgrsStts` |
|
||||
| 환경청 | 결과회신(자체종결) | `LE→EP` | `POST /fnrs-cnsltn-mng/{docNo}/tech-review` |
|
||||
| 환경청 | 기술검토 의뢰 | `LE→FV` | **신규** `POST /fnrs-cnsltn-mng/{docNo}/request-examine` |
|
||||
| 공단 접수 | 파일보완 팝업 | `ER` | `GET /fnrs-cnsltn-mng/{docNo}/file-vrfc-results` |
|
||||
| 공단 접수 | 파일보완요청 | `ER→SL` | `POST /fnrs-cnsltn-mng/{docNo}/reqsupplement` |
|
||||
| 공단 접수 | 파일검증 재실행 | `ER` 유지 | `POST /dcrag-to/documents/transfer` |
|
||||
| 공단 접수 | 검토자지정 | `ER→AC` | `POST /elapr-dmnd` (`elaprCycl: 1`) |
|
||||
| 공단 접수 | 검토자지정 | `EV→RK` | `POST /elapr-dmnd` (`elaprCycl: 2`) |
|
||||
| 공단 검토 | 실무협의서관리 | `TE`/`RK` | `GET .../pwrk-cnsltn-doc-crt-rslt/{uuid}/webhwp/open` |
|
||||
| 공단 검토 | 실무협의서생성 | `TE` 유지 | `POST /dcrag-to/documents/draft/generate` |
|
||||
| 공단 검토 | 저장 | `TE`/`RK` | `POST /pwrk-cnsltn-mng/save-apply` |
|
||||
| 공단 | 검토완료 / 결재(승인·회신) | `TE` 유지 / `TE→TP` / `RK→RR` | `PUT /elapr-prgrs-stts/{id}/process` |
|
||||
| 공단 검토 | 보완요청 | `TE→SL` / `RK→SL` | `POST /fnrs-cnsltn-mng/{docNo}/reqsupplement` |
|
||||
| 기초 | 파일보완완료 / 보완완료 | `SL→ER` / `TE` / `RK` | `POST /fnrs-cnsltn-mng/{docNo}/submitsupplement` |
|
||||
| 환경청 | 재원협의 결과 회신 | `TP→RM` | `POST /fnrs-cnsltn-mng/{docNo}/tech-review` |
|
||||
| 광역 | 지자체회신 | `RM→RB` | `updatePrgrsStts` |
|
||||
| 기초 | 실무협의서 관리 | `RB` 유지 | `GET .../pwrk-cnsltn-doc-crt-rslt/{uuid}/webhwp/open` |
|
||||
| 기초 | 조치완료 | `RB→RA` | **신규** `POST /pwrk-cnsltn-mng/{pwrkDocNo}/submit-actn-result` (`isSubmit: true`) |
|
||||
| 광역 | 조치결과서제출 | `RC→RA` | `updatePrgrsStts` |
|
||||
| 환경청 | 실무협의서 관리 | `RA` 유지 (조회) | `GET .../pwrk-cnsltn-doc-crt-rslt/{uuid}/webhwp/open` |
|
||||
| 환경청 | 조치결과 검토의뢰 | `RA→AV` | **신규** `POST /fnrs-cnsltn-mng/{docNo}/request-actn-review` |
|
||||
| 공단 검토 | 금액검증 재실행 | `RK` 유지 | `POST /dcrag-to/documents/cost/verify` |
|
||||
| 환경청 | 결과회신 | `RR→EP` | `updatePrgrsStts` |
|
||||
| 광역 | 결과회신 | `EP→TA` | `updatePrgrsStts` |
|
||||
| 웹기안기 | 저장 | 버전 +1 | `POST /pwrk-cnsltn-doc-crt-rslt` multipart |
|
||||
| 공통 | 다운로드 | — | `GET /pwrk-cnsltn-doc-crt-rslt/{uuid}/download` |
|
||||
| 광역 | 조치결과서제출 | `RC→RA` | `updatePrgrsStts` |
|
||||
| 환경청 | 실무협의서 관리 | `RA` 유지 (조회) | `GET .../pwrk-cnsltn-doc-crt-rslt/{uuid}/webhwp/open` |
|
||||
| 환경청 | 조치결과 검토의뢰 | `RA→AV` | **신규** `POST /fnrs-cnsltn-mng/{docNo}/request-actn-review` |
|
||||
| 공단 검토 | 금액검증 재실행 | `RK` 유지 | `POST /dcrag-to/documents/cost/verify` |
|
||||
| 환경청 | 결과회신 | `RR→EP` | `updatePrgrsStts` |
|
||||
| 광역 | 결과회신 | `EP→TA` | `updatePrgrsStts` |
|
||||
| 웹기안기 | 저장 | 버전 +1 | `POST /pwrk-cnsltn-doc-crt-rslt` multipart |
|
||||
| 공통 | 다운로드 | — | `GET /pwrk-cnsltn-doc-crt-rslt/{uuid}/download` |
|
||||
|
||||
신규 4개: `request-examine`, `submit-actn-result`, `request-actn-review`, (백엔드) `elapr-apln/copy-line`.
|
||||
신규 4개: `request-examine`, `submit-actn-result`, `request-actn-review`, EV 2차 `elapr-dmnd`.
|
||||
|
||||
---
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
| 27 | 조치완료 | `RB→RA` | as | `POST /api/v1/pwrk-cnsltn-mng/{pwrkDocNo}/submit-actn-result` (`isSubmit=true`) | **신규** |
|
||||
| 28 | 조치결과서제출 | `RC→RA` | as | `updatePrgrsStts` | 기존 |
|
||||
| 29 | 조치결과 검토의뢰 | `RA→AV` | as | `POST /api/v1/fnrs-cnsltn-mng/{docNo}/request-actn-review` | **신규** |
|
||||
| 30 | 금액검증 콜백 | `AV→RK` | as | `POST /api/v1/dcrag/cost/verify-result` | 서버 |
|
||||
| 30 | 금액검증 콜백 | `AV→EV` | as | `POST /api/v1/dcrag/cost/verify-result` | 서버 |
|
||||
| 30a | 검토자지정(2차) | `EV→RK` | elapr | `POST /api/v1/elapr-dmnd` (`elaprCycl=2`) | 기존 |
|
||||
| 31 | 실무협의서관리 | `RK` | as | `GET /pwrk-cnsltn-mng/{pwrkDocNo}` | 기존 |
|
||||
| 32 | 금액검증 재실행 | `RK` | as | `POST /api/v1/dcrag-to/documents/cost/verify` | 기존 |
|
||||
| 33 | 보완요청(금액) | `RK→SL` | as | `reqsupplement` | 기존 |
|
||||
@@ -212,7 +213,7 @@ v1.0 가이드의 “이 API를 프론트가 치면 안 된다”는 **폐기**.
|
||||
- `isSubmit: true` → `RB→RA` (조치완료)
|
||||
응답: `string` (pwrkDocNo). DTO명 `PwrkActnResultSubmitDto`.
|
||||
|
||||
### 3-6. `RA → AV → RK`
|
||||
### 3-6. `RA → AV → EV → RK`
|
||||
**2차 의뢰 (신규)** — `POST /fnrs-cnsltn-mng/{docNo}/request-actn-review`
|
||||
|
||||
```json
|
||||
@@ -223,7 +224,8 @@ v1.0 가이드의 “이 API를 프론트가 치면 안 된다”는 **폐기**.
|
||||
|
||||
`RA→AV`. 응답: `string` (docNo). DTO명 `EnvoActnReviewReqDto`. `specialRqstCn`만 선택.
|
||||
|
||||
금액검증 콜백은 서버. 콜백 후 서버가 `copy-line` 호출.
|
||||
금액검증 콜백은 서버 → `AV→EV`.
|
||||
**2차 검토자지정** — `POST /elapr-dmnd` (`elaprCycl: 2`, `docPrgrsSttsComCd: RK`). 1차 결재선(`findDocPrgrsByDocNo`, cycl=1)을 시드로 편집.
|
||||
|
||||
**금액검증 재실행** — `POST /dcrag-to/documents/cost/verify`
|
||||
|
||||
@@ -292,7 +294,7 @@ multipart `Content-Type` 헤더를 수동 지정하지 않는다 (boundary). JSO
|
||||
}
|
||||
```
|
||||
|
||||
`RK`에서 2차 결재선은 기존 `findDocPrgrsByDocNo` / `elaprCycl=2` 조회로 표시.
|
||||
`RK`에서 결재현황은 `findDocPrgrsByDocNo`(전체) 조회 후 `elaprCycl`로 1차/2차 탭 표시.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -343,5 +343,8 @@ docPrgrsSttsComCd: data.detDto?.prgrsSttsComCd, // ER
|
||||
|
||||
To-Be:
|
||||
|
||||
- 검토자지정은 **`ER`에서만**. `elaprCycl: 1`, `docPrgrsSttsComCd: "AC"`
|
||||
- 2차 결재선은 서버 `copy-line`. `RK`에서 검토자지정 팝업을 다시 열지 않음
|
||||
- 검토자지정은 **`ER`(1차)** 과 **`EV`(2차)** 에서.
|
||||
- `ER`: `elaprCycl: 1`, `docPrgrsSttsComCd: "AC"`, 서식 선택 다이얼로그
|
||||
- `EV`: `elaprCycl: 2`, `docPrgrsSttsComCd: "RK"`, 1차 결재선 시드 다이얼로그(서식 없음)
|
||||
|
||||
`RK` / ATRZ02 FAB: `findDocPrgrsByDocNo` 전체 조회 후 pending은 `elaprCycl === 2`만 사용. 검토완료 후 `ElaprApprovalDialog`에 `elaprCycl={2}`. 보완요청은 TE와 동일 `SplmntDmndDialog`.
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
2-Pass:
|
||||
|
||||
- **Pass 1** 기술 타당성: `SV→ST→LE→FV→ER→AC→TE→TP→RM→RB`
|
||||
- **Pass 2** 조치결과·금액: `RB→RA→AV→RK→RR→EP→TA`
|
||||
- **Pass 2** 조치결과·금액: `RB→RA→AV→EV→RK→RR→EP→TA`
|
||||
|
||||
자체종결(기술검토 생략): `LE→EP→TA`
|
||||
|
||||
@@ -80,9 +80,9 @@ sequenceDiagram
|
||||
rect rgb(240, 255, 255)
|
||||
광역->>환경청: [조치결과서제출] RC → RA
|
||||
환경청->>AI: [조치결과 검토의뢰] RA → AV
|
||||
AI-->>공단: 금액검증 콜백 AV → RK
|
||||
Note over 공단: 30분 미응답 시 RK + 수동 [금액검증]
|
||||
Note over 공단: AV→RK 시 1차 결재선 → 2차 복사 (서버)
|
||||
AI-->>공단: 금액검증 콜백 AV → EV
|
||||
공단->>공단: [검토자지정] EV → RK
|
||||
Note over 공단: EV에서 1차 결재선 시드로 2차 결재선 수동 배정
|
||||
end
|
||||
|
||||
rect rgb(255, 255, 230)
|
||||
@@ -125,7 +125,8 @@ sequenceDiagram
|
||||
| 9 | 검토결과 회신 | 환경청·광역 | `TP→RM→RB` | 1차 회신. `EP`와 혼동 금지 |
|
||||
| 10 | 조치결과 작성 | 기초 | `RB→RA` | `submit-actn-result` `isSubmit:true` (조치완료) |
|
||||
| 11 | 조치 의뢰 | 환경청 | `RA→AV` | `request-actn-review` |
|
||||
| 12 | AI 금액검증 | AI·공단 | `AV→RK` | 결재선 복사(서버) |
|
||||
| 12 | AI 금액검증 | AI·공단 | `AV→EV` | 콜백/Fallback 후 접수자 대기 |
|
||||
| 12a | 2차 검토자지정 | 공단 접수 | `EV→RK` | `elapr-dmnd` cycl=2 |
|
||||
| 13 | 2차 검토·결재 | 공단 | `RK→RR` / `SL` | 2차 결재선 |
|
||||
| 14 | 최종 회신 | 환경청·광역·기초 | `RR→EP→TA` | 종결 |
|
||||
|
||||
@@ -151,7 +152,8 @@ sequenceDiagram
|
||||
| `RC` | 조치결과서 제출 | Result Complete | 기초→광역 | `RB→RC` |
|
||||
| `RA` | 조치결과서 제출(환경청) | Result Ask to Basin | 기초→환경청 | `RB→RA` |
|
||||
| `AV` | 금액검증 | Amount Verification | AI | `RA→AV` |
|
||||
| `RK` | 조치결과서검토 | Result Check | 공단 2차 결재 | `AV→RK`, `SL→RK` |
|
||||
| `EV` | 조치결과검토의뢰 | Examine Verify | 공단 접수자 | `AV→EV` |
|
||||
| `RK` | 조치결과서검토 | Result Check | 공단 2차 결재 | `EV→RK`, `SL→RK` |
|
||||
| `RR` | 조치결과서검토 회신 | Result Return to Basin | 공단→환경청 | `RK→RR` |
|
||||
| `EP` | 검토결과 회신 | Examine Pass to Province | 환경청→광역 | `LE→EP`(자체), `RR→EP`(최종) |
|
||||
| `TA` | 검토결과 회신(종료) | Technical Approval | 광역→기초 | `EP→TA` |
|
||||
@@ -200,7 +202,7 @@ stateDiagram-v2
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| AI-1 | 파일 무결성 | `FV→ER` | 환경청 [기술검토 의뢰] | 30분 → `ER` + [파일검증] | `POST /dcrag-to/documents/transfer` |
|
||||
| AI-2 | 초안 생성 | `AC→TE` | 공단 [검토자지정] | 30분 → `TE` + [실무협의서생성] | `POST /dcrag-to/documents/draft/generate` |
|
||||
| AI-3 | 금액 검증 | `AV→RK` | 환경청 [조치결과 검토의뢰] | 30분 → `RK` + [금액검증] | `POST /dcrag-to/documents/cost/verify` |
|
||||
| AI-3 | 금액 검증 | `AV→EV` | 환경청 [조치결과 검토의뢰] | 30분 → `EV` + 수동 [금액검증] | `POST /dcrag-to/documents/cost/verify` |
|
||||
|
||||
- 예상 소요 안내는 **30분** (v1.0의 10분은 폐기).
|
||||
- 콜백 수신·스케줄러 전이는 서버. FE는 상세 refetch / draft status 폴링만.
|
||||
@@ -221,14 +223,12 @@ stateDiagram-v2
|
||||
|
||||
---
|
||||
|
||||
## 7. 결재선 복사
|
||||
|
||||
규칙: `"금액검증후: 2차결재선 1차결재선 복사생성"`
|
||||
## 7. 2차 결재선 (수동 검토자지정)
|
||||
|
||||
1. `TE`에서 1차 결재선(`elaprCycl = 1`) 완료.
|
||||
2. `AV→RK` 콜백 시 서버가 `POST /elapr-apln/copy-line` (`source=1`, `target=2`).
|
||||
3. `RK` 화면은 2차 결재선을 조회해 검토자/승인자/최종승인자를 표시.
|
||||
4. 프론트는 copy-line을 **호출하지 않는다.**
|
||||
2. 금액검증 콜백/Fallback → `AV→EV`.
|
||||
3. 공단 접수자가 `EV`에서 [검토자지정]: 1차 결재선을 시드로 편집 후 `POST /elapr-dmnd` (`elaprCycl: 2`) → `RK`.
|
||||
4. `copy-line` 자동 복사는 사용하지 않는다.
|
||||
|
||||
결재 `process`:
|
||||
|
||||
|
||||
@@ -62,16 +62,17 @@ API 컬럼은 [ref-endpoint.md](./ref-endpoint.md)와 동일.
|
||||
| 27 | 상세 | USER04 | - | `RC` | 조치결과서제출 | `RA` | 환경청 | `updatePrgrsStts` |
|
||||
| 28 | 상세 | USER03 | - | `RA` | 실무협의서 관리 | 유지 | — | 웹기안기 Open. **저장 불가** |
|
||||
| 28a | 상세 | USER03 | - | `RA` | 조치결과 검토의뢰 | `AV` | 공단 검토 | **`request-actn-review`** |
|
||||
| 29 | 자동화 | AI | - | `AV` | (금액검증) | `RK` | 공단 검토 | FE 호출 없음. 진행 패널 |
|
||||
| 29 | 자동화 | AI | - | `AV` | (금액검증) | `EV` | 공단 접수 | FE 호출 없음. 진행 패널 |
|
||||
| 29a | 상세 | USER01 | 접수자 | `EV` | 검토자지정 | `RK` | 공단 검토 | `POST /elapr-dmnd` cycl=2. 1차 결재선 시드 |
|
||||
| 30 | 상세 | USER01 | 검토자 | `RK` | 실무협의서관리 | 유지 | — | 웹기안기 |
|
||||
| 31 | 상세 | USER01 | 검토자 | `RK` | 금액검증 | `RK` | — | `dcrag-to/cost/verify` |
|
||||
| 32 | 상세 | USER01 | 검토자 | `RK` | 저장 | `RK` | — | `pwrk save-apply` |
|
||||
| 33 | 상세 | USER01 | 검토자 | `RK` | 검토완료 | `RK` | 다음 결재자 | `process` |
|
||||
| 34 | 상세 | USER01 | 승인자 | `RK` | 결재(승인) | `RK` | 다음 결재자 | `process` |
|
||||
| 35 | 상세 | USER01 | 최종승인자 | `RK` | 결재(회신) | `RR` | 환경청 | `process` 최종 |
|
||||
| 36 | 상세 | USER03 | - | `RR` | 결과회신 | `EP` | 광역 | `updatePrgrsStts` |
|
||||
| 37 | 작성/상세 | USER04 | - | `EP` | 결과회신 | `TA` | 기초 | `updatePrgrsStts` |
|
||||
| 38 | 상세 | USER05 | - | `TA` | 결과회신 | — | — | **API 없음. 조회** |
|
||||
| 36 | 상세 | USER03 | - | `RR` | 실무협의서 관리(조회), 검토결과 회신 | `EP` | 광역 | `tech-review` |
|
||||
| 37 | 작성/상세 | USER04 | - | `EP` | 결과회신, 실무협의서 관리(조회) | `TA` | 기초 | `updatePrgrsStts` |
|
||||
| 38 | 상세 | USER05 | - | `TA` | 결과회신, 실무협의서 관리(조회) | — | — | **API 없음. 조회** |
|
||||
|
||||
`SL` 20~22행: 버튼은 직전 상태에 따라 **하나만** 노출. 파일 루프면 [파일보완완료], 그 외 [보완완료].
|
||||
|
||||
@@ -85,7 +86,7 @@ API 컬럼은 [ref-endpoint.md](./ref-endpoint.md)와 동일.
|
||||
| --- | --- | --- |
|
||||
| `fileSplmnt` | 파일보완 | `ER` 접수자. 현행에 없음 |
|
||||
| `fileVrfc` | 파일검증 | 유지. `ER` 재실행 |
|
||||
| `dsgntRvwr` | 검토자지정 | 유지. 전이를 `AC`로 |
|
||||
| `dsgntRvwr` | 검토자지정 | `ER→AC`(1차), `EV→RK`(2차) |
|
||||
| `pwrkCnsltnDocMng` | 실무협의서관리 | 유지. `TE`·`RK`·`RB`·`RA`(조회 전용) |
|
||||
| `pwrkCnsltnDocCrt` | 실무협의서생성 | 유지. 실패/타임아웃만 |
|
||||
| `rvwRsltInq` | 검토결과조회 | 신규 |
|
||||
@@ -108,19 +109,19 @@ ROLE_USER05
|
||||
"" | SV → 저장, 신청완료, 삭제
|
||||
SL → (직전 ER) 파일보완완료 | (직전 TE|RK) 보완완료
|
||||
RB → 실무협의서 관리, 조치완료
|
||||
TA → 결과회신 (조회)
|
||||
TA → 결과회신 (조회), 실무협의서 관리(조회)
|
||||
|
||||
ROLE_USER04
|
||||
ST → 환경청제출 // 현행 반려(rjct→SV)는 SoT 38행에 없음. 유지 시 별도 합의
|
||||
RM → 지자체회신
|
||||
RC → 조치결과서제출
|
||||
EP → 결과회신
|
||||
EP → 결과회신, 실무협의서 관리(조회)
|
||||
|
||||
ROLE_USER03
|
||||
LE → 결과회신(자체종결), 기술검토 의뢰
|
||||
TP → 재원협의 결과 회신
|
||||
RA → 실무협의서 관리(조회), 조치결과 검토의뢰
|
||||
RR → 결과회신
|
||||
RR → 실무협의서 관리(조회), 검토결과 회신 (`tech-review` → `EP`)
|
||||
```
|
||||
|
||||
### 3-2. 공단 결재선 × 상태
|
||||
@@ -129,6 +130,9 @@ ROLE_USER03
|
||||
ER / ATRZ01 접수자
|
||||
→ 파일보완, 파일검증, 검토자지정
|
||||
|
||||
EV / ATRZ01 접수자
|
||||
→ 검토자지정 (2차 결재선. 1차 결재선 시드, 서식 선택 없음)
|
||||
|
||||
TE / ATRZ02 검토자
|
||||
→ (초안 성공) 실무협의서관리, 검토결과조회, 저장, 검토완료, 보완요청
|
||||
→ (초안 실패·타임아웃) 실무협의서생성 (+ 위 항목 중 관리/조회는 초안 생긴 뒤)
|
||||
@@ -137,14 +141,18 @@ TE / ATRZ03 승인자 → 결재(승인)
|
||||
TE / ATRZ99 최종 → 결재(회신)
|
||||
|
||||
RK / ATRZ02 검토자
|
||||
→ 실무협의서관리, 금액검증, 저장, 검토완료, 보완요청
|
||||
RK / ATRZ03 → 결재(승인)
|
||||
RK / ATRZ99 → 결재(회신)
|
||||
→ (초안 완료) 실무협의서관리
|
||||
→ (초안 완료 + 결재 대기 ATRZ_PRGRS_002, cycl2) 보완요청, 검토완료
|
||||
· 보완요청 UI: TE와 동일 SplmntDmndDialog
|
||||
RK / ATRZ03 → (초안 완료) 실무협의서관리, (cycl2 pending) 결재(승인)
|
||||
RK / ATRZ99 → (초안 완료) 실무협의서관리, (cycl2 pending) 결재(회신)
|
||||
· 결재 UI: ElaprApprovalDialog (elaprCycl=2) 승인/반려 → PUT process
|
||||
```
|
||||
|
||||
`FV`, `AC`, `AV`: 액션 빈 배열 + 진행 패널.
|
||||
|
||||
현행 `TE`에서 `hasPendingApprovalRequest === false`이면 버튼을 숨긴다. v3.0에서는 검토자 버튼(저장·웹기안기·보완)은 결재 대기와 무관하게 두고, **검토완료/결재**만 결재건 존재 여부에 묶는다.
|
||||
`PENDING_APPROVAL_ACTIONS`: `dmndSplmnt`, `rvwCmptn`, `atrzAprv`, `atrzRply`만.
|
||||
`pwrkCnsltnDocMng`는 결재 대기와 무관(초안 완료만). ATRZ02·ATRZ03·ATRZ99라도 pending 없으면 관리만 노출.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
"@hookform/devtools": "^4.4.0",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"eslint": "^9.0.0",
|
||||
"eslint-config-next": "15.3.8",
|
||||
|
||||
@@ -6,36 +6,57 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en-us">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>Hwp SDK Wasm Sample</title>
|
||||
<script type="text/javascript" data-type="css/app" data-framework="aori|lab" src="js/loadVersion.js"></script>
|
||||
<script type="text/javascript" data-type="css/app" data-framework="aori|lab" src="js/loadHead.js"></script>
|
||||
<script data-main="js/main" data-bundle="dist/main_bundle" src="js/loadMain.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="status" style="display:none;">
|
||||
<div class="sppiner-content">
|
||||
<div class="spinner" id='spinner'></div>
|
||||
<div class="emscripten" id="statustext"></div>
|
||||
</div>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>Hwp SDK Wasm Sample</title>
|
||||
<script
|
||||
type="text/javascript"
|
||||
data-type="css/app"
|
||||
data-framework="aori|lab"
|
||||
src="js/loadVersion.js"
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
data-type="css/app"
|
||||
data-framework="aori|lab"
|
||||
src="js/loadHead.js"
|
||||
></script>
|
||||
<script data-main="js/main" data-bundle="dist/main_bundle" src="js/loadMain.js"></script>
|
||||
<style>
|
||||
#hcwoViewScroll::-webkit-scrollbar {
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
}
|
||||
#hcwoViewScroll::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
border-radius: 5px;
|
||||
}
|
||||
#hcwoViewScroll::-webkit-scrollbar-track {
|
||||
background: rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="status" style="display: none">
|
||||
<div class="sppiner-content">
|
||||
<div class="spinner" id="spinner"></div>
|
||||
<div class="emscripten" id="statustext"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="status_downloading">
|
||||
<div class="status-content">
|
||||
<div class="emscriptenDownloading">
|
||||
<span id="statusDownloadingText">구성 중 ... 0% </span>
|
||||
<span id="statusDownloadingPercent" class="emscriptenDownloadingPercent"></span>
|
||||
</div>
|
||||
<div class="emscriptenDownloadingInfo"></div>
|
||||
<progress id="download_progress" value="0" max="100"></progress>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="status_downloading">
|
||||
<div class="status-content">
|
||||
<div class="emscriptenDownloading">
|
||||
<span id="statusDownloadingText">구성 중 ... 0% </span>
|
||||
<span id="statusDownloadingPercent" class="emscriptenDownloadingPercent"></span>
|
||||
</div>
|
||||
<div class="emscriptenDownloadingInfo"></div>
|
||||
<progress id="download_progress" value="0" max="100"></progress>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<iframe id="download_iframe" style="width:0px;height:0px;border:0;display:none;"></iframe>
|
||||
<script type="text/javascript" src="hwpsdk/loadHwpsdk.js"></script>
|
||||
</body>
|
||||
<iframe id="download_iframe" style="width: 0px; height: 0px; border: 0; display: none"></iframe>
|
||||
<script type="text/javascript" src="hwpsdk/loadHwpsdk.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
|
||||
import type {
|
||||
AiAutoPrcsLatestResponseDto,
|
||||
AiAutoPrcsMngService,
|
||||
} from "../model/ai-auto-prcs-mng-interface";
|
||||
|
||||
const BASE = ENDPOINT.AS_SERVICE.AI_AUTO_PRCS_MNG;
|
||||
|
||||
async function findLatestPerAutoSeByFnrsDocNo(
|
||||
fnrsCnsltnDocNo: string,
|
||||
atmtnSeComCd?: string,
|
||||
): Promise<AiAutoPrcsLatestResponseDto[]> {
|
||||
const response = await client.get<AiAutoPrcsLatestResponseDto[]>(
|
||||
`${BASE}/fnrs/${fnrsCnsltnDocNo}/latest`,
|
||||
{ params: atmtnSeComCd ? { atmtnSeComCd } : undefined },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export const aiAutoPrcsMngService = {
|
||||
findLatestPerAutoSeByFnrsDocNo,
|
||||
} satisfies AiAutoPrcsMngService;
|
||||
@@ -0,0 +1,30 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
|
||||
import { aiAutoPrcsMngService } from "./ai-auto-prcs-mng-service";
|
||||
|
||||
export const aiAutoPrcsMngKeys = {
|
||||
all: [ENDPOINT.AS_SERVICE.AI_AUTO_PRCS_MNG] as const,
|
||||
latest: (fnrsCnsltnDocNo: string, atmtnSeComCd?: string) =>
|
||||
[...aiAutoPrcsMngKeys.all, "latest", fnrsCnsltnDocNo, atmtnSeComCd ?? "all"] as const,
|
||||
};
|
||||
|
||||
interface UseAiAutoPrcsLatestOptions {
|
||||
enabled?: boolean;
|
||||
atmtnSeComCd?: string;
|
||||
}
|
||||
|
||||
export function useAiAutoPrcsLatest(
|
||||
fnrsCnsltnDocNo: string | null | undefined,
|
||||
options: UseAiAutoPrcsLatestOptions = {},
|
||||
) {
|
||||
const { enabled = true, atmtnSeComCd } = options;
|
||||
|
||||
return useQuery({
|
||||
queryKey: aiAutoPrcsMngKeys.latest(fnrsCnsltnDocNo!, atmtnSeComCd),
|
||||
queryFn: () =>
|
||||
aiAutoPrcsMngService.findLatestPerAutoSeByFnrsDocNo(fnrsCnsltnDocNo!, atmtnSeComCd),
|
||||
enabled: Boolean(fnrsCnsltnDocNo) && enabled,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export { aiAutoPrcsMngService } from "./api/ai-auto-prcs-mng-service";
|
||||
export { aiAutoPrcsMngKeys, useAiAutoPrcsLatest } from "./api/queries";
|
||||
export type {
|
||||
AiAutoPrcsLatestResponseDto,
|
||||
AiAutoPrcsMngService,
|
||||
AiAutoSeComCd,
|
||||
} from "./model/ai-auto-prcs-mng-interface";
|
||||
export { AI_AUTO_SE } from "./model/ai-auto-prcs-mng-interface";
|
||||
@@ -0,0 +1,44 @@
|
||||
/** 자동화구분 공통코드 (AIAUTOSE) */
|
||||
export const AI_AUTO_SE = {
|
||||
FILE_VRFC: "FILE_VRFC",
|
||||
DRAFT_GEN: "DRAFT_GEN",
|
||||
COST_VRFC: "COST_VRFC",
|
||||
} as const;
|
||||
|
||||
export type AiAutoSeComCd = (typeof AI_AUTO_SE)[keyof typeof AI_AUTO_SE];
|
||||
|
||||
/** 백엔드 AiAutoPrcsLatestResponseDto */
|
||||
export interface AiAutoPrcsLatestResponseDto {
|
||||
atmtnPrcsUuid?: string;
|
||||
fnrsCnsltnDocNo?: string;
|
||||
pwrkCnsltnDocNo?: string;
|
||||
crtDmndUuid?: string;
|
||||
atmtnSeComCd?: string;
|
||||
atmtnSeComNm?: string;
|
||||
bfrDocSttsComCd?: string;
|
||||
bfrDocSttsComNm?: string;
|
||||
aftrDocSttsComCd?: string;
|
||||
aftrDocSttsComNm?: string;
|
||||
atmtnPrcsSttsComCd?: string;
|
||||
atmtnPrcsSttsComNm?: string;
|
||||
dmndDt?: string;
|
||||
dmndSysId?: string;
|
||||
rplyDt?: string;
|
||||
rplyRsltComCd?: string;
|
||||
prcsErrCn?: string | null;
|
||||
lastSttsInqDt?: string;
|
||||
sttsInqRsltComCd?: string;
|
||||
rtrsmCtpvNmtm?: number;
|
||||
lastRtrsmDt?: string;
|
||||
lastFrcSttsChgYn?: string;
|
||||
isFailed?: boolean;
|
||||
isRetryable?: boolean;
|
||||
}
|
||||
|
||||
/** Controller public 메서드명·시그니처와 동일 */
|
||||
export interface AiAutoPrcsMngService {
|
||||
findLatestPerAutoSeByFnrsDocNo(
|
||||
fnrsCnsltnDocNo: string,
|
||||
atmtnSeComCd?: string,
|
||||
): Promise<AiAutoPrcsLatestResponseDto[]>;
|
||||
}
|
||||
@@ -6,3 +6,7 @@ export {
|
||||
} from "./api/queries";
|
||||
export { parseFilenameFromResponse } from "./lib/parse-filename-from-response";
|
||||
export type { Attach, AttachService } from "./model/attach-interface";
|
||||
export {
|
||||
AttachmentDownloadLink,
|
||||
type AttachmentDownloadLinkProps,
|
||||
} from "./ui/attachment-download-link";
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
"use client";
|
||||
|
||||
import { Paperclip } from "lucide-react";
|
||||
|
||||
import { cn } from "@common/components";
|
||||
|
||||
import { useDownloadAttachmentByAtflCd, useDownloadAttachmentByAtflId } from "../api/queries";
|
||||
|
||||
interface AttachmentDownloadLinkProps {
|
||||
atflCd?: string;
|
||||
atflId?: string;
|
||||
fileName?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function AttachmentDownloadLink({
|
||||
atflCd,
|
||||
atflId,
|
||||
fileName,
|
||||
className,
|
||||
}: AttachmentDownloadLinkProps) {
|
||||
const downloadByAtflCd = useDownloadAttachmentByAtflCd();
|
||||
const downloadByAtflId = useDownloadAttachmentByAtflId();
|
||||
const displayName = fileName || "첨부파일";
|
||||
const canDownload = Boolean(atflId || atflCd);
|
||||
const isPending = atflId ? downloadByAtflId.isPending : downloadByAtflCd.isPending;
|
||||
|
||||
function handleClick() {
|
||||
if (atflId) {
|
||||
downloadByAtflId.mutate(atflId);
|
||||
return;
|
||||
}
|
||||
if (atflCd) downloadByAtflCd.mutate(atflCd);
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
"inline-flex cursor-pointer items-start gap-1 text-left text-blue-600 hover:underline disabled:cursor-wait disabled:opacity-60",
|
||||
className,
|
||||
)}
|
||||
aria-label={`${displayName} 다운로드`}
|
||||
disabled={isPending || !canDownload}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<Paperclip className="my-1 size-5 shrink-0" />
|
||||
{displayName}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export { AttachmentDownloadLink };
|
||||
export type { AttachmentDownloadLinkProps };
|
||||
@@ -46,9 +46,40 @@ async function requestCostVerification(
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function reTransferDocument(
|
||||
body: DcRagDocumentTransferRequestDto,
|
||||
): Promise<DcRagCommonResponseDto> {
|
||||
const response = await client.post<DcRagCommonResponseDto>(
|
||||
`${BASE}/documents/re-transfer`,
|
||||
body,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function reGenerateDraft(
|
||||
body: DcRagDraftGenerateRequestDto,
|
||||
): Promise<DcRagDraftGenerateResponseDto> {
|
||||
const response = await client.post<DcRagDraftGenerateResponseDto>(
|
||||
`${BASE}/documents/draft/re-generate`,
|
||||
body,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function reVerifyCost(body: DcRagCostVerifyRequestDto): Promise<DcRagCommonResponseDto> {
|
||||
const response = await client.post<DcRagCommonResponseDto>(
|
||||
`${BASE}/documents/cost/re-verify`,
|
||||
body,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export const dcRagApiToService = {
|
||||
requestDocumentTransfer,
|
||||
requestDraftGeneration,
|
||||
getDraftGenerationStatus,
|
||||
requestCostVerification,
|
||||
reTransferDocument,
|
||||
reGenerateDraft,
|
||||
reVerifyCost,
|
||||
} satisfies DcRagApiToService;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useMutationWrapper } from "@/shared/lib";
|
||||
import { aiAutoPrcsMngKeys } from "@/entities/ai-auto-prcs-mng";
|
||||
import { fnrsCnsltnKeys } from "@/entities/fnrs-cnsltn";
|
||||
|
||||
import type { DcRagDraftStatusResponseDto } from "../model/dcrag-to-interface";
|
||||
import type {
|
||||
@@ -21,6 +24,18 @@ function isDraftGenerationSettled(data: DcRagDraftStatusResponseDto | undefined)
|
||||
return data.events?.some((event) => event.eventType === "COMPLETED") ?? false;
|
||||
}
|
||||
|
||||
function invalidateAfterAiRetry(
|
||||
queryClient: ReturnType<typeof useQueryClient>,
|
||||
fnrsCnsltnDocNo?: string,
|
||||
) {
|
||||
queryClient.invalidateQueries({ queryKey: dcRagApiToBaseKey });
|
||||
queryClient.invalidateQueries({ queryKey: aiAutoPrcsMngKeys.all });
|
||||
if (fnrsCnsltnDocNo) {
|
||||
queryClient.invalidateQueries({ queryKey: fnrsCnsltnKeys.detail(fnrsCnsltnDocNo) });
|
||||
queryClient.invalidateQueries({ queryKey: fnrsCnsltnKeys.all });
|
||||
}
|
||||
}
|
||||
|
||||
export function useDraftGenerationStatus(crtDmndUuid: string | null | undefined) {
|
||||
return useQuery({
|
||||
queryKey: [...dcRagApiToBaseKey, "getDraftGenerationStatus", crtDmndUuid],
|
||||
@@ -68,3 +83,39 @@ export function useRequestCostVerification() {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useReTransferDocument() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutationWrapper({
|
||||
mutationFn: (body: DcRagDocumentTransferRequestDto) =>
|
||||
dcRagApiToService.reTransferDocument(body),
|
||||
onSuccess: (_data, body) => {
|
||||
invalidateAfterAiRetry(queryClient, body.fnrsCnsltnDocNo);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useReGenerateDraft() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutationWrapper({
|
||||
mutationFn: (body: DcRagDraftGenerateRequestDto) => dcRagApiToService.reGenerateDraft(body),
|
||||
onSuccess: (_data, body) => {
|
||||
invalidateAfterAiRetry(queryClient, body.fnrsCnsltnDocNo);
|
||||
if (body.crtDmndUuid) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [...dcRagApiToBaseKey, "getDraftGenerationStatus", body.crtDmndUuid],
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useReVerifyCost() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutationWrapper({
|
||||
mutationFn: (body: DcRagCostVerifyRequestDto) => dcRagApiToService.reVerifyCost(body),
|
||||
onSuccess: (_data, body) => {
|
||||
invalidateAfterAiRetry(queryClient, body.fnrsCnsltnDocNo);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,9 +2,12 @@ export { dcRagApiToService } from "./api/dcrag-to-service";
|
||||
export { dcRagApiToBaseKey } from "./api/queries";
|
||||
export {
|
||||
useDraftGenerationStatus,
|
||||
useReGenerateDraft,
|
||||
useRequestCostVerification,
|
||||
useRequestDocumentTransfer,
|
||||
useRequestDraftGeneration,
|
||||
useReTransferDocument,
|
||||
useReVerifyCost,
|
||||
} from "./api/queries";
|
||||
export type {
|
||||
DcRagApiToService,
|
||||
|
||||
@@ -49,4 +49,7 @@ export interface DcRagApiToService {
|
||||
): Promise<DcRagDraftGenerateResponseDto>;
|
||||
getDraftGenerationStatus(crtDmndUuid: string): Promise<DcRagDraftStatusResponseDto>;
|
||||
requestCostVerification(body: DcRagCostVerifyRequestDto): Promise<DcRagCommonResponseDto>;
|
||||
reTransferDocument(body: DcRagDocumentTransferRequestDto): Promise<DcRagCommonResponseDto>;
|
||||
reGenerateDraft(body: DcRagDraftGenerateRequestDto): Promise<DcRagDraftGenerateResponseDto>;
|
||||
reVerifyCost(body: DcRagCostVerifyRequestDto): Promise<DcRagCommonResponseDto>;
|
||||
}
|
||||
|
||||
@@ -4,9 +4,11 @@ import type { PageResponse } from "@/shared/model";
|
||||
|
||||
import type {
|
||||
DesignCnstnApplySaveRequestDto,
|
||||
DesignCnstnMngSaveRequestDto,
|
||||
} from "../model/design-cnstn-apply-payload-schema";
|
||||
import type {
|
||||
DesignCnstnMngDetResponseDto,
|
||||
DesignCnstnMngResponseDto,
|
||||
DesignCnstnMngSaveRequestDto,
|
||||
DesignCnstnMngSearchDto,
|
||||
DesignCnstnMngService,
|
||||
DesignCnstnMngUpdateRequestDto,
|
||||
|
||||
@@ -2,10 +2,10 @@ import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import type { PageResponse } from "@/shared/model";
|
||||
|
||||
import type { DesignCnstnRvwOpnnSaveRequestDto } from "../model/design-cnstn-apply-payload-schema";
|
||||
import type {
|
||||
DesignCnstnRvwOpnnId,
|
||||
DesignCnstnRvwOpnnResponseDto,
|
||||
DesignCnstnRvwOpnnSaveRequestDto,
|
||||
DesignCnstnRvwOpnnSearchDto,
|
||||
DesignCnstnRvwOpnnService,
|
||||
DesignCnstnRvwOpnnUpdateRequestDto,
|
||||
|
||||
@@ -18,11 +18,13 @@ import type {
|
||||
import type {
|
||||
DesignCnstnApplySaveRequestDto,
|
||||
DesignCnstnMngSaveRequestDto,
|
||||
DesignCnstnRvwOpnnSaveRequestDto,
|
||||
} from "../model/design-cnstn-apply-payload-schema";
|
||||
import type {
|
||||
DesignCnstnMngSearchDto,
|
||||
DesignCnstnMngUpdateRequestDto,
|
||||
} from "../model/design-cnstn-mng-interface";
|
||||
import type {
|
||||
DesignCnstnRvwOpnnSaveRequestDto,
|
||||
DesignCnstnRvwOpnnSearchDto,
|
||||
DesignCnstnRvwOpnnUpdateRequestDto,
|
||||
} from "../model/design-cnstn-rvw-opnn-interface";
|
||||
|
||||
@@ -37,6 +37,22 @@ export {
|
||||
useUpdateDesignCnstnMng,
|
||||
useUpdateDesignCnstnRvwOpnn,
|
||||
} from "./api/queries";
|
||||
export type {
|
||||
BizCstItem,
|
||||
BzScaleItem,
|
||||
DesignCnstnApplySaveRequestDto,
|
||||
DesignCnstnMngSaveRequestDto,
|
||||
DesignCnstnRvwOpnnSaveRequestDto,
|
||||
EsntlAtchFileItem,
|
||||
} from "./model/design-cnstn-apply-payload-schema";
|
||||
export {
|
||||
designCnstnApplySaveSchema,
|
||||
designCnstnBizCstItemSchema,
|
||||
designCnstnBzScaleItemSchema,
|
||||
designCnstnEsntlAtchFileItemSchema,
|
||||
designCnstnMngSaveSchema,
|
||||
designCnstnRvwOpnnSaveSchema,
|
||||
} from "./model/design-cnstn-apply-payload-schema";
|
||||
export type {
|
||||
DesignCnstnBizCstId,
|
||||
DesignCnstnBizCstResponseDto,
|
||||
@@ -63,21 +79,15 @@ export type {
|
||||
} from "./model/design-cnstn-esntl-atch-file-interface";
|
||||
export type {
|
||||
AtflMngClientResponseDto,
|
||||
BizCstItem,
|
||||
BizCstTreeDto,
|
||||
BizSclTreeDto,
|
||||
BzScaleItem,
|
||||
DesignCnstnApplySaveRequestDto,
|
||||
DesignCnstnMngDetResponseDto,
|
||||
DesignCnstnMngResponseDto,
|
||||
DesignCnstnMngSaveRequestDto,
|
||||
DesignCnstnMngSearchDto,
|
||||
DesignCnstnMngService,
|
||||
DesignCnstnMngUpdateRequestDto,
|
||||
DesignCnstnRvwOpnnResponseDto,
|
||||
DesignCnstnRvwOpnnSaveRequestDto,
|
||||
ElaprDocPrgrsClientResponseDto,
|
||||
EsntlAtchFileItem,
|
||||
EsntlAtchFileMngTreeResponseDto,
|
||||
} from "./model/design-cnstn-mng-interface";
|
||||
export type {
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import { z } from "zod";
|
||||
|
||||
/** 백엔드 DesignCnstnMngSaveRequestDto */
|
||||
export const designCnstnMngSaveSchema = z.object({
|
||||
designCnstnDocNo: z.string().optional(),
|
||||
admdstCd: z.string().optional(),
|
||||
envoSeComCd: z.string().optional(),
|
||||
taskSeComCd: z.string().optional(),
|
||||
bizNm: z.string().optional(),
|
||||
bizBgngYr: z.string().optional(),
|
||||
bizEndYr: z.string().optional(),
|
||||
bizCn: z.string().optional(),
|
||||
aplcntNm: z.string().optional(),
|
||||
aplcntMblTelno: z.string().optional(),
|
||||
aplcntOgdpNm: z.string().optional(),
|
||||
aplcntEmlAddr: z.string().optional(),
|
||||
dsgnrNm: z.string().optional(),
|
||||
dsgnrTelno: z.string().optional(),
|
||||
dsgnrOgdpNm: z.string().optional(),
|
||||
dsgnrEmlAddr: z.string().optional(),
|
||||
atchFileGroupId: z.string().optional(),
|
||||
prgrsSttsComCd: z.string().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 DesignCnstnApplySaveRequestDto.BzScaleItem */
|
||||
export const designCnstnBzScaleItemSchema = z.object({
|
||||
cnstpSeCd: z.string().optional(),
|
||||
bscPl: z.number().optional(),
|
||||
budgetReflct: z.number().optional(),
|
||||
thtiDsgn: z.number().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 DesignCnstnApplySaveRequestDto.BizCstItem */
|
||||
export const designCnstnBizCstItemSchema = z.object({
|
||||
acntgSeCd: z.string().optional(),
|
||||
bscPlanAmt: z.number().optional(),
|
||||
bgtRfltAmt: z.number().optional(),
|
||||
tstmDesignAmt: z.number().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 DesignCnstnApplySaveRequestDto.EsntlAtchFileItem */
|
||||
export const designCnstnEsntlAtchFileItemSchema = z.object({
|
||||
esntlAtchFileId: z.string().optional(),
|
||||
atchFileGroupId: z.string().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 DesignCnstnRvwOpnnSaveRequestDto */
|
||||
export const designCnstnRvwOpnnSaveSchema = z.object({
|
||||
designCnstnDocNo: z.string(),
|
||||
rvwOpnnSn: z.number().optional(),
|
||||
opnrSeComCd: z.string().optional(),
|
||||
opnrNm: z.string().optional(),
|
||||
rvwRsltCn: z.string().optional(),
|
||||
rvwRsltAnmlyYn: z.string().optional(),
|
||||
atchFileGroupId: z.string().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 DesignCnstnApplySaveRequestDto */
|
||||
export const designCnstnApplySaveSchema = z.object({
|
||||
techReViewSaveCd: z.string().optional(),
|
||||
rvwStpCd: z.string().optional(),
|
||||
designCnstnMng: designCnstnMngSaveSchema,
|
||||
bzScaleList: z.array(designCnstnBzScaleItemSchema).optional(),
|
||||
bizCstList: z.array(designCnstnBizCstItemSchema).optional(),
|
||||
esntlAtchFileList: z.array(designCnstnEsntlAtchFileItemSchema).optional(),
|
||||
rvwOpnnList: z.array(designCnstnRvwOpnnSaveSchema).optional(),
|
||||
});
|
||||
|
||||
export type DesignCnstnMngSaveRequestDto = z.infer<typeof designCnstnMngSaveSchema>;
|
||||
export type BzScaleItem = z.infer<typeof designCnstnBzScaleItemSchema>;
|
||||
export type BizCstItem = z.infer<typeof designCnstnBizCstItemSchema>;
|
||||
export type EsntlAtchFileItem = z.infer<typeof designCnstnEsntlAtchFileItemSchema>;
|
||||
export type DesignCnstnRvwOpnnSaveRequestDto = z.infer<typeof designCnstnRvwOpnnSaveSchema>;
|
||||
export type DesignCnstnApplySaveRequestDto = z.infer<typeof designCnstnApplySaveSchema>;
|
||||
@@ -1,22 +1,12 @@
|
||||
import type {
|
||||
BizCstSaveBase,
|
||||
BzScaleSaveBase,
|
||||
EsntlAtchFileSaveBase,
|
||||
PageableParams,
|
||||
PageResponse,
|
||||
PbadmsApplySaveHeader,
|
||||
PbadmsMngDetResponseBase,
|
||||
} from "@/shared/model";
|
||||
import type { PageableParams, PageResponse, PbadmsMngDetResponseBase } from "@/shared/model";
|
||||
|
||||
import type {
|
||||
DesignCnstnRvwOpnnResponseDto,
|
||||
DesignCnstnRvwOpnnSaveRequestDto,
|
||||
} from "./design-cnstn-rvw-opnn-interface";
|
||||
DesignCnstnApplySaveRequestDto,
|
||||
DesignCnstnMngSaveRequestDto,
|
||||
} from "./design-cnstn-apply-payload-schema";
|
||||
import type { DesignCnstnRvwOpnnResponseDto } from "./design-cnstn-rvw-opnn-interface";
|
||||
|
||||
export type {
|
||||
DesignCnstnRvwOpnnResponseDto,
|
||||
DesignCnstnRvwOpnnSaveRequestDto,
|
||||
} from "./design-cnstn-rvw-opnn-interface";
|
||||
export type { DesignCnstnRvwOpnnResponseDto } from "./design-cnstn-rvw-opnn-interface";
|
||||
export type {
|
||||
AtflMngClientResponseDto,
|
||||
BizCstTreeDto,
|
||||
@@ -25,28 +15,6 @@ export type {
|
||||
EsntlAtchFileMngTreeResponseDto,
|
||||
} from "@/shared/model";
|
||||
|
||||
/** 백엔드 DesignCnstnMngSaveRequestDto */
|
||||
export interface DesignCnstnMngSaveRequestDto {
|
||||
designCnstnDocNo?: string;
|
||||
admdstCd?: string;
|
||||
envoSeComCd?: string;
|
||||
taskSeComCd?: string;
|
||||
bizNm?: string;
|
||||
bizBgngYr?: string;
|
||||
bizEndYr?: string;
|
||||
bizCn?: string;
|
||||
aplcntNm?: string;
|
||||
aplcntMblTelno?: string;
|
||||
aplcntOgdpNm?: string;
|
||||
aplcntEmlAddr?: string;
|
||||
dsgnrNm?: string;
|
||||
dsgnrTelno?: string;
|
||||
dsgnrOgdpNm?: string;
|
||||
dsgnrEmlAddr?: string;
|
||||
atchFileGroupId?: string;
|
||||
prgrsSttsComCd?: string;
|
||||
}
|
||||
|
||||
/** 백엔드 DesignCnstnMngUpdateRequestDto */
|
||||
export type DesignCnstnMngUpdateRequestDto = Omit<DesignCnstnMngSaveRequestDto, "designCnstnDocNo">;
|
||||
|
||||
@@ -88,30 +56,12 @@ interface DesignCnstnMngResponseExtension {
|
||||
export type DesignCnstnMngResponseDto = Omit<DesignCnstnMngSaveRequestDto, "designCnstnDocNo"> &
|
||||
DesignCnstnMngResponseExtension;
|
||||
|
||||
/** 백엔드 DesignCnstnApplySaveRequestDto.BzScaleItem */
|
||||
export type BzScaleItem = BzScaleSaveBase;
|
||||
|
||||
/** 백엔드 DesignCnstnApplySaveRequestDto.BizCstItem */
|
||||
export type BizCstItem = BizCstSaveBase;
|
||||
|
||||
/** 백엔드 DesignCnstnApplySaveRequestDto.EsntlAtchFileItem */
|
||||
export type EsntlAtchFileItem = EsntlAtchFileSaveBase;
|
||||
|
||||
/** 백엔드 DesignCnstnMngDetResponseDto */
|
||||
export type DesignCnstnMngDetResponseDto = PbadmsMngDetResponseBase<
|
||||
DesignCnstnMngResponseDto,
|
||||
DesignCnstnRvwOpnnResponseDto
|
||||
>;
|
||||
|
||||
/** 백엔드 DesignCnstnApplySaveRequestDto */
|
||||
export interface DesignCnstnApplySaveRequestDto extends PbadmsApplySaveHeader {
|
||||
designCnstnMng: DesignCnstnMngSaveRequestDto;
|
||||
bzScaleList?: BzScaleItem[];
|
||||
bizCstList?: BizCstItem[];
|
||||
esntlAtchFileList?: EsntlAtchFileItem[];
|
||||
rvwOpnnList?: DesignCnstnRvwOpnnSaveRequestDto[];
|
||||
}
|
||||
|
||||
/** Controller public 메서드명·시그니처와 동일 */
|
||||
export interface DesignCnstnMngService {
|
||||
findPage(searchDto: DesignCnstnMngSearchDto): Promise<PageResponse<DesignCnstnMngResponseDto>>;
|
||||
|
||||
@@ -1,22 +1,13 @@
|
||||
import type { PageableParams, PageResponse, RvwOpnnResponseExtension } from "@/shared/model";
|
||||
|
||||
import type { DesignCnstnRvwOpnnSaveRequestDto } from "./design-cnstn-apply-payload-schema";
|
||||
|
||||
/** 백엔드 DesignCnstnRvwOpnnId */
|
||||
export interface DesignCnstnRvwOpnnId {
|
||||
designCnstnMng: string;
|
||||
rvwOpnnSn: number;
|
||||
}
|
||||
|
||||
/** 백엔드 DesignCnstnRvwOpnnSaveRequestDto */
|
||||
export interface DesignCnstnRvwOpnnSaveRequestDto {
|
||||
designCnstnDocNo: string;
|
||||
rvwOpnnSn?: number;
|
||||
opnrSeComCd?: string;
|
||||
opnrNm?: string;
|
||||
rvwRsltCn?: string;
|
||||
rvwRsltAnmlyYn?: string;
|
||||
atchFileGroupId?: string;
|
||||
}
|
||||
|
||||
/** 백엔드 DesignCnstnRvwOpnnUpdateRequestDto */
|
||||
export interface DesignCnstnRvwOpnnUpdateRequestDto {
|
||||
opnrSeComCd?: string;
|
||||
|
||||
@@ -49,9 +49,13 @@ async function remove(elaprDmndId: string): Promise<void> {
|
||||
await client.delete(`${BASE}/${elaprDmndId}`);
|
||||
}
|
||||
|
||||
async function findDocPrgrsByDocNo(elaprDocNo: string): Promise<ElaprDocPrgrsResponseDto[]> {
|
||||
async function findDocPrgrsByDocNo(
|
||||
elaprDocNo: string,
|
||||
elaprCycl?: number,
|
||||
): Promise<ElaprDocPrgrsResponseDto[]> {
|
||||
const response = await client.get<ElaprDocPrgrsResponseDto[]>(
|
||||
`${BASE}/sanc/docprgrs/${elaprDocNo}`,
|
||||
{ params: elaprCycl != null ? { elaprCycl } : undefined },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
@@ -185,11 +185,16 @@ export function useElaprDmnd(elaprDmndId: string | null | undefined) {
|
||||
});
|
||||
}
|
||||
|
||||
export function useElaprDocPrgrsByDocNo(elaprDocNo: string | null | undefined) {
|
||||
export function useElaprDocPrgrsByDocNo(
|
||||
elaprDocNo: string | null | undefined,
|
||||
options?: { elaprCycl?: number; enabled?: boolean; structuralSharing?: boolean },
|
||||
) {
|
||||
const elaprCycl = options?.elaprCycl;
|
||||
return useQuery({
|
||||
queryKey: [...elaprDmndBaseKey, "findDocPrgrsByDocNo", elaprDocNo],
|
||||
queryFn: () => elaprDmndService.findDocPrgrsByDocNo(elaprDocNo!),
|
||||
enabled: Boolean(elaprDocNo),
|
||||
queryKey: [...elaprDmndBaseKey, "findDocPrgrsByDocNo", elaprDocNo, elaprCycl],
|
||||
queryFn: () => elaprDmndService.findDocPrgrsByDocNo(elaprDocNo!, elaprCycl),
|
||||
enabled: Boolean(elaprDocNo) && (options?.enabled ?? true),
|
||||
structuralSharing: options?.structuralSharing ?? true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -284,13 +289,8 @@ export function useProcessElaprPrgrsStts() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({
|
||||
elaprPrgrsId,
|
||||
body,
|
||||
}: {
|
||||
elaprPrgrsId: string;
|
||||
body: ElaprProcessRequestDto;
|
||||
}) => elaprPrgrsSttsService.process(elaprPrgrsId, body),
|
||||
mutationFn: ({ elaprPrgrsId, body }: { elaprPrgrsId: string; body: ElaprProcessRequestDto }) =>
|
||||
elaprPrgrsSttsService.process(elaprPrgrsId, body),
|
||||
onSuccess: () => {
|
||||
invalidateElaprPrgrsSttsQueries(queryClient);
|
||||
},
|
||||
|
||||
@@ -57,6 +57,7 @@ export {
|
||||
buildElaprPrgrsSttsListFromApln,
|
||||
buildElaprPrgrsSttsListFromAplnRows,
|
||||
} from "./lib/dmnd-save-utils";
|
||||
export { mapDocPrgrsToAplnRows } from "./lib/doc-prgrs-to-apln-rows";
|
||||
export type { ElaprPrgrsSttsRow } from "./lib/prgrs-stts-save-utils";
|
||||
export {
|
||||
assignPrgrsSttsAtrzSeqNumbers,
|
||||
@@ -113,6 +114,10 @@ export type { ReviewerOption } from "./model/reviewer-option";
|
||||
export { AprvrPickerDialog } from "./ui/aprvr-picker-dialog";
|
||||
export { ElaprAplnPickerGrid } from "./ui/elapr-apln-picker-grid";
|
||||
export { ElaprApprovalDialog } from "./ui/elapr-approval-dialog";
|
||||
export {
|
||||
type ElaprDocPrgrsPickerConfirmPayload,
|
||||
ElaprDocPrgrsPickerDialog,
|
||||
} from "./ui/elapr-doc-prgrs-picker-dialog";
|
||||
export { ElaprDocPrgrsStatus } from "./ui/elapr-doc-prgrs-status";
|
||||
export {
|
||||
type ElaprTmpltPickerConfirmPayload,
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { ElaprDocPrgrsClientResponseDto } from "@/shared/model";
|
||||
|
||||
import type { ElaprApln } from "../model/elapr-apln-interface";
|
||||
|
||||
/** 문서 결재진행 → 결재선 그리드 시드 (ElaprApln 호환) */
|
||||
export function mapDocPrgrsToAplnRows(
|
||||
list: ElaprDocPrgrsClientResponseDto[] | undefined,
|
||||
): ElaprApln[] {
|
||||
if (!list?.length) return [];
|
||||
|
||||
return list.map((item, index) => ({
|
||||
aplnId: item.elaprPrgrsId ?? `prgrs-${item.atrzSeqNo ?? index}`,
|
||||
elaprTmpltId: "",
|
||||
atrzSeqNo: item.atrzSeqNo,
|
||||
atrzSeComCd: item.atrzSeComCd,
|
||||
atrzSeComNm: item.atrzSeComNm,
|
||||
aprvrUuid: item.aprvrUuid,
|
||||
aprvrNm: item.aprvrNm,
|
||||
aprvrUserId: item.aprvrUserId,
|
||||
}));
|
||||
}
|
||||
@@ -22,33 +22,35 @@ function toRow(item: ElaprApln): ElaprAplnRow {
|
||||
}
|
||||
|
||||
interface UseAplnRowsOptions {
|
||||
elaprTmpltId: string | null;
|
||||
seedKey: string | null;
|
||||
serverRows: ElaprApln[] | undefined;
|
||||
elaprTmpltId?: string | null;
|
||||
}
|
||||
|
||||
export function createDraftRowKey(): string {
|
||||
return `draft-${Date.now()}`;
|
||||
}
|
||||
|
||||
export function useAplnRows({ elaprTmpltId, serverRows }: UseAplnRowsOptions) {
|
||||
export function useAplnRows({ seedKey, serverRows, elaprTmpltId = "" }: UseAplnRowsOptions) {
|
||||
const [rows, setRows] = useState<ElaprAplnRow[]>([]);
|
||||
const [editingRowKey, setEditingRowKey] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!elaprTmpltId) {
|
||||
if (!seedKey) {
|
||||
setRows([]);
|
||||
setEditingRowKey(null);
|
||||
return;
|
||||
}
|
||||
setRows((serverRows ?? []).map(toRow));
|
||||
setEditingRowKey(null);
|
||||
}, [elaprTmpltId, serverRows]);
|
||||
}, [seedKey, serverRows]);
|
||||
|
||||
const isEditingLocked = editingRowKey != null;
|
||||
const visibleRows = getVisibleRows(rows);
|
||||
const canAdd = Boolean(seedKey) && !isEditingLocked;
|
||||
|
||||
const addRow = useCallback(() => {
|
||||
if (!elaprTmpltId || isEditingLocked) return;
|
||||
if (!canAdd) return;
|
||||
|
||||
const rowKey = createDraftRowKey();
|
||||
|
||||
@@ -56,7 +58,7 @@ export function useAplnRows({ elaprTmpltId, serverRows }: UseAplnRowsOptions) {
|
||||
...prev,
|
||||
{
|
||||
rowKey,
|
||||
elaprTmpltId,
|
||||
elaprTmpltId: elaprTmpltId ?? "",
|
||||
atrzSeComCd: "",
|
||||
aprvrUuid: "",
|
||||
aprvrNm: "",
|
||||
@@ -66,7 +68,7 @@ export function useAplnRows({ elaprTmpltId, serverRows }: UseAplnRowsOptions) {
|
||||
},
|
||||
]);
|
||||
setEditingRowKey(rowKey);
|
||||
}, [elaprTmpltId, isEditingLocked]);
|
||||
}, [canAdd, elaprTmpltId]);
|
||||
|
||||
const startEdit = useCallback(
|
||||
(rowKey: string) => {
|
||||
|
||||
@@ -45,5 +45,5 @@ export interface ElaprDmndService {
|
||||
update(elaprDmndId: string, body: ElaprDmndSaveRequestDto): Promise<ElaprDmndResponseDto>;
|
||||
updateStatus(elaprDmndId: string, atrzSttsComCd: string): Promise<ElaprDmndResponseDto>;
|
||||
remove(elaprDmndId: string): Promise<void>;
|
||||
findDocPrgrsByDocNo(elaprDocNo: string): Promise<ElaprDocPrgrsResponseDto[]>;
|
||||
findDocPrgrsByDocNo(elaprDocNo: string, elaprCycl?: number): Promise<ElaprDocPrgrsResponseDto[]>;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import { useElaprAplnPage } from "../api/queries";
|
||||
import { validateAplnRow } from "../lib/apln-validators";
|
||||
import { useAplnRows } from "../lib/use-apln-rows";
|
||||
import { useDndSensors } from "../lib/use-dnd-sensors";
|
||||
import type { ElaprApln } from "../model/elapr-apln-interface";
|
||||
import type { ElaprAplnRow } from "../model/elapr-apln-row-types";
|
||||
import type { ReviewerOption } from "../model/reviewer-option";
|
||||
|
||||
@@ -27,13 +28,17 @@ import { AprvrPickerDialog } from "./aprvr-picker-dialog";
|
||||
import { SortableAplnPickerRow } from "./sortable-apln-picker-row";
|
||||
|
||||
interface ElaprAplnPickerGridProps {
|
||||
elaprTmpltId: string | null;
|
||||
elaprTmpltId?: string | null;
|
||||
externalServerRows?: ElaprApln[];
|
||||
isExternalLoading?: boolean;
|
||||
onRowsChange?: (rows: ElaprAplnRow[]) => void;
|
||||
onEditingLockedChange?: (locked: boolean) => void;
|
||||
}
|
||||
|
||||
function ElaprAplnPickerGrid({
|
||||
elaprTmpltId,
|
||||
elaprTmpltId = null,
|
||||
externalServerRows,
|
||||
isExternalLoading = false,
|
||||
onRowsChange,
|
||||
onEditingLockedChange,
|
||||
}: ElaprAplnPickerGridProps) {
|
||||
@@ -41,14 +46,18 @@ function ElaprAplnPickerGrid({
|
||||
const [pickingRowKey, setPickingRowKey] = useState<string | null>(null);
|
||||
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
const isExternalMode = externalServerRows != null;
|
||||
|
||||
const aplnQuery = useElaprAplnPage({
|
||||
elaprTmpltId: elaprTmpltId ?? undefined,
|
||||
elaprTmpltId: !isExternalMode ? (elaprTmpltId ?? undefined) : undefined,
|
||||
page: 0,
|
||||
size: 200,
|
||||
sort: "atrzSeqNo,asc",
|
||||
});
|
||||
|
||||
const seedKey = isExternalMode ? "external-doc-prgrs" : elaprTmpltId;
|
||||
const serverRows = isExternalMode ? externalServerRows : aplnQuery.data?.content;
|
||||
|
||||
const {
|
||||
rows,
|
||||
visibleRows,
|
||||
@@ -62,8 +71,9 @@ function ElaprAplnPickerGrid({
|
||||
markDeleted,
|
||||
reorderRows,
|
||||
} = useAplnRows({
|
||||
elaprTmpltId,
|
||||
serverRows: aplnQuery.data?.content,
|
||||
seedKey,
|
||||
serverRows,
|
||||
elaprTmpltId: elaprTmpltId ?? "",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -75,8 +85,12 @@ function ElaprAplnPickerGrid({
|
||||
}, [isEditingLocked, onEditingLockedChange]);
|
||||
|
||||
const sensors = useDndSensors();
|
||||
const isDisabled = !elaprTmpltId;
|
||||
const isReady = isExternalMode || Boolean(elaprTmpltId);
|
||||
const isLoading = isExternalMode
|
||||
? isExternalLoading
|
||||
: aplnQuery.isPending && Boolean(elaprTmpltId);
|
||||
const pickingRow = pickingRowKey ? rows.find((r) => r.rowKey === pickingRowKey) : undefined;
|
||||
const excludedVisibleRows = visibleRows.filter((r) => r.atrzSeComCd !== "ATRZ01");
|
||||
|
||||
function handleDragEnd(event: DragEndEvent) {
|
||||
const { active, over } = event;
|
||||
@@ -121,7 +135,7 @@ function ElaprAplnPickerGrid({
|
||||
size="sm"
|
||||
type="button"
|
||||
color="green"
|
||||
disabled={isDisabled || isEditingLocked}
|
||||
disabled={!isReady || isEditingLocked}
|
||||
onClick={addRow}
|
||||
>
|
||||
결재선 추가
|
||||
@@ -129,15 +143,15 @@ function ElaprAplnPickerGrid({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{aplnQuery.isPending && elaprTmpltId ? (
|
||||
{isLoading ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
<Skeleton className="h-[4rem]" />
|
||||
<Skeleton className="h-[16rem]" />
|
||||
</div>
|
||||
) : !elaprTmpltId ? (
|
||||
) : !isReady ? (
|
||||
<p className="py-8 text-center text-lg text-muted-foreground">결재서식을 선택하세요.</p>
|
||||
) : visibleRows.length === 0 ? (
|
||||
<p className="py-8 text-center text-lg text-muted-foreground">
|
||||
) : excludedVisibleRows.length === 0 ? (
|
||||
<p className="py-16 text-center text-lg text-muted-foreground">
|
||||
등록된 결재선이 없습니다. [결재선 추가]로 결재자를 등록하세요.
|
||||
</p>
|
||||
) : (
|
||||
@@ -161,10 +175,10 @@ function ElaprAplnPickerGrid({
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<SortableContext
|
||||
items={visibleRows.map((r) => r.rowKey)}
|
||||
items={excludedVisibleRows.map((r) => r.rowKey)}
|
||||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
{visibleRows.map((row, index) => (
|
||||
{excludedVisibleRows.map((row, index) => (
|
||||
<SortableAplnPickerRow
|
||||
key={row.rowKey}
|
||||
row={row}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
import { closestCenter, DndContext, type DragEndEvent } from "@dnd-kit/core";
|
||||
import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";
|
||||
@@ -48,6 +48,7 @@ interface ElaprApprovalDialogProps {
|
||||
onOpenChange: (open: boolean) => void;
|
||||
elaprDocNo: string;
|
||||
currentUserUuid: string;
|
||||
elaprCycl?: number;
|
||||
onSuccess: () => void;
|
||||
}
|
||||
|
||||
@@ -58,6 +59,7 @@ function ElaprApprovalDialog({
|
||||
onOpenChange,
|
||||
elaprDocNo,
|
||||
currentUserUuid,
|
||||
elaprCycl,
|
||||
onSuccess,
|
||||
}: ElaprApprovalDialogProps) {
|
||||
const queryClient = useQueryClient();
|
||||
@@ -67,9 +69,16 @@ function ElaprApprovalDialog({
|
||||
const [pickerOpen, setPickerOpen] = useState(false);
|
||||
const [pickingRowKey, setPickingRowKey] = useState<string | null>(null);
|
||||
|
||||
const { data: elaprDocPrgrsList, isLoading } = useElaprDocPrgrsByDocNo(open ? elaprDocNo : null);
|
||||
const { data: elaprDocPrgrsList, isLoading } = useElaprDocPrgrsByDocNo(open ? elaprDocNo : null, {
|
||||
elaprCycl,
|
||||
structuralSharing: false,
|
||||
});
|
||||
const cyclDocPrgrsList = useMemo(() => {
|
||||
if (elaprCycl == null) return elaprDocPrgrsList;
|
||||
return elaprDocPrgrsList?.filter((row) => Number(row.elaprCycl) === elaprCycl);
|
||||
}, [elaprCycl, elaprDocPrgrsList]);
|
||||
|
||||
const elaprDmndId = elaprDocPrgrsList?.[0]?.elaprDmndId;
|
||||
const elaprDmndId = cyclDocPrgrsList?.[0]?.elaprDmndId;
|
||||
|
||||
const {
|
||||
rows,
|
||||
@@ -85,22 +94,19 @@ function ElaprApprovalDialog({
|
||||
markDeleted,
|
||||
reorderRows,
|
||||
} = usePrgrsSttsRows({
|
||||
serverRows: elaprDocPrgrsList,
|
||||
serverRows: cyclDocPrgrsList,
|
||||
elaprDmndId,
|
||||
});
|
||||
|
||||
const savePrgrsSttsMutation = useSaveElaprPrgrsStts();
|
||||
const sensors = useDndSensors();
|
||||
|
||||
const currentUserPrgrs = findCurrentUserPendingApprovalRequest(
|
||||
elaprDocPrgrsList,
|
||||
currentUserUuid,
|
||||
);
|
||||
const currentUserPrgrs = findCurrentUserPendingApprovalRequest(cyclDocPrgrsList, currentUserUuid);
|
||||
const canApprove = Boolean(currentUserPrgrs?.elaprPrgrsId);
|
||||
|
||||
const pickingRow = pickingRowKey ? rows.find((r) => r.rowKey === pickingRowKey) : undefined;
|
||||
const isBusy = isProcessing || savePrgrsSttsMutation.isPending;
|
||||
const isPrgrsSttsUnsaved = hasUnsavedPrgrsSttsChanges(rows, elaprDocPrgrsList ?? []);
|
||||
const isPrgrsSttsUnsaved = hasUnsavedPrgrsSttsChanges(rows, cyclDocPrgrsList ?? []);
|
||||
|
||||
function handleDragEnd(event: DragEndEvent) {
|
||||
const { active, over } = event;
|
||||
@@ -164,7 +170,7 @@ function ElaprApprovalDialog({
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = buildPrgrsSttsSavePayload(rows, elaprDmndId, elaprDocPrgrsList ?? []);
|
||||
const payload = buildPrgrsSttsSavePayload(rows, elaprDmndId, cyclDocPrgrsList ?? []);
|
||||
if (payload.length === 0) {
|
||||
alert("변경된 결재선이 없습니다.");
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@common/components";
|
||||
|
||||
import { useElaprDocPrgrsByDocNo } from "../api/queries";
|
||||
import {
|
||||
getVisibleRows,
|
||||
validateAllRowsForSave,
|
||||
validateAtrzOrder,
|
||||
validateUniqueSeqNo,
|
||||
} from "../lib/apln-validators";
|
||||
import { mapDocPrgrsToAplnRows } from "../lib/doc-prgrs-to-apln-rows";
|
||||
import type { ElaprAplnRow } from "../model/elapr-apln-row-types";
|
||||
|
||||
import { ElaprAplnPickerGrid } from "./elapr-apln-picker-grid";
|
||||
|
||||
interface ElaprDocPrgrsPickerConfirmPayload {
|
||||
aplnRows: ElaprAplnRow[];
|
||||
}
|
||||
|
||||
interface ElaprDocPrgrsPickerDialogProps {
|
||||
open: boolean;
|
||||
elaprDocNo: string;
|
||||
onClose: () => void;
|
||||
onConfirm?: (payload: ElaprDocPrgrsPickerConfirmPayload) => void;
|
||||
}
|
||||
|
||||
function ElaprDocPrgrsPickerDialog({
|
||||
open,
|
||||
elaprDocNo,
|
||||
onClose,
|
||||
onConfirm,
|
||||
}: ElaprDocPrgrsPickerDialogProps) {
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
const [aplnRows, setAplnRows] = useState<ElaprAplnRow[]>([]);
|
||||
const [isEditingLocked, setIsEditingLocked] = useState(false);
|
||||
|
||||
const docPrgrsQuery = useElaprDocPrgrsByDocNo(elaprDocNo, {
|
||||
elaprCycl: 1,
|
||||
enabled: open && Boolean(elaprDocNo),
|
||||
});
|
||||
|
||||
const externalServerRows = useMemo(
|
||||
() => mapDocPrgrsToAplnRows(docPrgrsQuery.data),
|
||||
[docPrgrsQuery.data],
|
||||
);
|
||||
|
||||
const handleRowsChange = useCallback((rows: ElaprAplnRow[]) => {
|
||||
setAplnRows(rows);
|
||||
}, []);
|
||||
|
||||
const handleEditingLockedChange = useCallback((locked: boolean) => {
|
||||
setIsEditingLocked(locked);
|
||||
}, []);
|
||||
|
||||
function handleOpenChange(isOpen: boolean) {
|
||||
if (!isOpen) {
|
||||
setAplnRows([]);
|
||||
setIsEditingLocked(false);
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
|
||||
function handleConfirm() {
|
||||
const rowsError = validateAllRowsForSave(aplnRows, isEditingLocked);
|
||||
if (rowsError) {
|
||||
alert(rowsError);
|
||||
return;
|
||||
}
|
||||
|
||||
const orderError = validateAtrzOrder(aplnRows);
|
||||
if (orderError) {
|
||||
alert(orderError);
|
||||
return;
|
||||
}
|
||||
|
||||
const seqError = validateUniqueSeqNo(aplnRows);
|
||||
if (seqError) {
|
||||
alert(seqError);
|
||||
return;
|
||||
}
|
||||
|
||||
const visibleRows = getVisibleRows(aplnRows);
|
||||
onConfirm?.({ aplnRows: visibleRows });
|
||||
|
||||
setAplnRows([]);
|
||||
setIsEditingLocked(false);
|
||||
onClose();
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||
<DialogContent className="max-w-[80rem]" aria-describedby={undefined}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>결재선 선택</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<ElaprAplnPickerGrid
|
||||
externalServerRows={externalServerRows}
|
||||
isExternalLoading={docPrgrsQuery.isPending}
|
||||
onRowsChange={handleRowsChange}
|
||||
onEditingLockedChange={handleEditingLockedChange}
|
||||
/>
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="button" color="white" onClick={() => handleOpenChange(false)}>
|
||||
닫기
|
||||
</Button>
|
||||
<Button type="button" disabled={isEditingLocked} onClick={handleConfirm}>
|
||||
확인
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export { ElaprDocPrgrsPickerDialog };
|
||||
export type { ElaprDocPrgrsPickerConfirmPayload };
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { Fragment } from "react";
|
||||
import { Fragment, useEffect, useMemo, useState } from "react";
|
||||
|
||||
import { ChevronRight } from "lucide-react";
|
||||
|
||||
@@ -8,7 +8,7 @@ import { dayjs, useUserStore } from "@/shared/lib";
|
||||
import type { ElaprDocPrgrsClientResponseDto } from "@/shared/model";
|
||||
import { SectionCollapsible } from "@/shared/ui";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/shared/ui/tooltip";
|
||||
import { cn, Skeleton } from "@common/components";
|
||||
import { cn, Skeleton, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import { useElaprDocPrgrsByDocNo } from "../api/queries";
|
||||
import { getApproverStatus, sortByAtrzSeqNo } from "../lib/approval-utils";
|
||||
@@ -67,10 +67,46 @@ function ElaprDocPrgrsStep({ item }: { item: ElaprDocPrgrsClientResponseDto }) {
|
||||
);
|
||||
}
|
||||
|
||||
function ElaprDocPrgrsStepList({ steps }: { steps: ElaprDocPrgrsClientResponseDto[] }) {
|
||||
if (steps.length === 0) {
|
||||
return <p className="px-4 py-6 text-xl text-muted-foreground">결재선이 없습니다.</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-stretch justify-start gap-4 overflow-x-auto">
|
||||
{steps.map((item, index) => (
|
||||
<Fragment key={item.elaprPrgrsId ?? `${item.atrzSeqNo}-${item.aprvrUuid ?? index}`}>
|
||||
{index > 0 && (
|
||||
<div className="flex items-center">
|
||||
<ChevronRight className="size-8 text-muted-foreground" />
|
||||
</div>
|
||||
)}
|
||||
<ElaprDocPrgrsStep item={item} />
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ElaprDocPrgrsStatus({ elaprDocNo }: ElaprDocPrgrsStatusProps) {
|
||||
const userInfo = useUserStore((state) => state.userInfo);
|
||||
const { data, isLoading } = useElaprDocPrgrsByDocNo(elaprDocNo);
|
||||
const steps = sortByAtrzSeqNo(data ?? []);
|
||||
const [activeCycl, setActiveCycl] = useState<"1" | "2">("1");
|
||||
|
||||
const { cycl1Steps, cycl2Steps, hasCycl2 } = useMemo(() => {
|
||||
const rows = data ?? [];
|
||||
const cycl1 = sortByAtrzSeqNo(rows.filter((item) => Number(item.elaprCycl ?? 1) !== 2));
|
||||
const cycl2 = sortByAtrzSeqNo(rows.filter((item) => Number(item.elaprCycl) === 2));
|
||||
return {
|
||||
cycl1Steps: cycl1,
|
||||
cycl2Steps: cycl2,
|
||||
hasCycl2: cycl2.length > 0,
|
||||
};
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
setActiveCycl(hasCycl2 ? "2" : "1");
|
||||
}, [hasCycl2]);
|
||||
|
||||
if (userInfo?.dtlInstCd !== "ROLE_USER01") {
|
||||
return null;
|
||||
@@ -88,24 +124,40 @@ function ElaprDocPrgrsStatus({ elaprDocNo }: ElaprDocPrgrsStatusProps) {
|
||||
);
|
||||
}
|
||||
|
||||
if (steps.length === 0) {
|
||||
if (cycl1Steps.length === 0 && cycl2Steps.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SectionCollapsible title="결재현황">
|
||||
<div className="flex flex-wrap items-stretch justify-start gap-4 overflow-x-auto p-4">
|
||||
{steps.map((item, index) => (
|
||||
<Fragment key={item.elaprPrgrsId ?? `${item.atrzSeqNo}-${item.aprvrUuid ?? index}`}>
|
||||
{index > 0 && (
|
||||
<div className="flex items-center">
|
||||
{<ChevronRight className="size-8 text-muted-foreground" />}
|
||||
</div>
|
||||
)}
|
||||
<ElaprDocPrgrsStep item={item} />
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
{hasCycl2 ? (
|
||||
<Tabs
|
||||
value={activeCycl}
|
||||
onValueChange={(value) => setActiveCycl(value as "1" | "2")}
|
||||
className="p-2"
|
||||
>
|
||||
<TabsList>
|
||||
<TabsTrigger value="1" className="!px-6 !py-3 text-xl">
|
||||
재원협의
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="2" className="!px-6 !py-3 text-xl">
|
||||
실무협의서
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="1" className="mt-2 border-none p-0 shadow-none">
|
||||
<div className="-mx-2">
|
||||
<ElaprDocPrgrsStepList steps={cycl1Steps} />
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="2" className="mt-2 border-none p-0 shadow-none">
|
||||
<div className="-mx-2">
|
||||
<ElaprDocPrgrsStepList steps={cycl2Steps} />
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
) : (
|
||||
<ElaprDocPrgrsStepList steps={cycl1Steps} />
|
||||
)}
|
||||
</SectionCollapsible>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,12 +3,14 @@ import { ENDPOINT } from "@/shared/config";
|
||||
import type { PageResponse } from "@/shared/model";
|
||||
|
||||
import type {
|
||||
DocPrgrsUpdateRequestDto,
|
||||
FnrsCnsltnApplySaveRequestDto,
|
||||
FnrsCnsltnMngSaveRequestDto,
|
||||
} from "../model/fnrs-cnsltn-apply-payload-schema";
|
||||
import type {
|
||||
DocPrgrsUpdateRequestDto,
|
||||
FnrsCnsltnFileVrfcRsltResponseDto,
|
||||
FnrsCnsltnMngDetResponseDto,
|
||||
FnrsCnsltnMngResponseDto,
|
||||
FnrsCnsltnMngSaveRequestDto,
|
||||
FnrsCnsltnMngSearchDto,
|
||||
FnrsCnsltnMngService,
|
||||
FnrsCnsltnMngUpdateRequestDto,
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import type {
|
||||
DocPrgrsUpdateRequestDto,
|
||||
FnrsCnsltnApplySaveRequestDto,
|
||||
FnrsCnsltnMngSaveRequestDto,
|
||||
} from "../model/fnrs-cnsltn-apply-payload-schema";
|
||||
import type {
|
||||
DocPrgrsUpdateRequestDto,
|
||||
FnrsCnsltnMngSearchDto,
|
||||
FnrsCnsltnMngUpdateRequestDto,
|
||||
} from "../model/fnrs-cnsltn-interface";
|
||||
|
||||
@@ -16,30 +16,44 @@ export {
|
||||
useUpdateFnrsCnsltnMng,
|
||||
} from "./api/queries";
|
||||
export type {
|
||||
AtflMngClientResponseDto,
|
||||
BizCstItem,
|
||||
BizCstTreeDto,
|
||||
BizSclTreeDto,
|
||||
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,
|
||||
EsntlAtchFileItem,
|
||||
EsntlAtchFileMngTreeResponseDto,
|
||||
FnrsCnsltnApplySaveRequestDto,
|
||||
FnrsCnsltnFileVrfcRsltResponseDto,
|
||||
FnrsCnsltnMngDetResponseDto,
|
||||
FnrsCnsltnMngResponseDto,
|
||||
FnrsCnsltnMngSaveRequestDto,
|
||||
FnrsCnsltnMngSearchDto,
|
||||
FnrsCnsltnMngService,
|
||||
FnrsCnsltnMngUpdateRequestDto,
|
||||
FnrsCnsltnRvwOpnnResponseDto,
|
||||
FnrsCnsltnRvwOpnnSaveRequestDto,
|
||||
FnrsCnsltnSplmntDmndItem,
|
||||
FnrsCnsltnSplmntDmndResponseDto,
|
||||
FnrsSharItem,
|
||||
RagIndexingInfoDto,
|
||||
} from "./model/fnrs-cnsltn-interface";
|
||||
export type {
|
||||
EnvoActnReviewReqDto,
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
import { z } from "zod";
|
||||
|
||||
/** 백엔드 FnrsCnsltnMngSaveRequestDto */
|
||||
export const fnrsCnsltnMngSaveSchema = z.object({
|
||||
fnrsCnsltnDocNo: z.string().optional(),
|
||||
admdstCd: z.string().optional(),
|
||||
envoSeComCd: z.string().optional(),
|
||||
taskSeComCd: z.string().optional(),
|
||||
cfmtnBgtBizNo: z.string().optional(),
|
||||
bgtAplyYr: z.string().optional(),
|
||||
bizNm: z.string().optional(),
|
||||
bizBgngYr: z.string().optional(),
|
||||
bizEndYr: z.string().optional(),
|
||||
bizCn: z.string().optional(),
|
||||
aplcntNm: z.string().optional(),
|
||||
aplcntMblTelno: z.string().optional(),
|
||||
aplcntOgdpNm: z.string().optional(),
|
||||
aplcntEmlAddr: z.string().optional(),
|
||||
dsgnrNm: z.string().optional(),
|
||||
dsgnrTelno: z.string().optional(),
|
||||
dsgnrOgdpNm: z.string().optional(),
|
||||
dsgnrEmlAddr: z.string().optional(),
|
||||
atchFileGroupId: z.string().optional(),
|
||||
sharAtchFileGroupId: z.string().optional(),
|
||||
prgrsSttsComCd: z.string().optional(),
|
||||
rtlztnFcltSeComCd: z.string().optional(),
|
||||
rtlztnUsgSeComCd: z.string().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 FnrsCnsltnApplySaveRequestDto.BzScaleItem */
|
||||
export const fnrsCnsltnBzScaleItemSchema = z.object({
|
||||
cnstpSeCd: z.string().optional(),
|
||||
bscPl: z.number().optional(),
|
||||
budgetReflct: z.number().optional(),
|
||||
thtiDsgn: z.number().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 FnrsCnsltnApplySaveRequestDto.BizCstItem */
|
||||
export const fnrsCnsltnBizCstItemSchema = z.object({
|
||||
acntgSeCd: z.string().optional(),
|
||||
bscPlanAmt: z.number().optional(),
|
||||
bgtRfltAmt: z.number().optional(),
|
||||
tstmDesignAmt: z.number().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 FnrsCnsltnApplySaveRequestDto.CklstResultItem */
|
||||
export const fnrsCnsltnCklstResultItemSchema = z.object({
|
||||
cklstRvwSn: z.number().optional(),
|
||||
cklstArtclSn: z.number().optional(),
|
||||
cklstSn: z.number().optional(),
|
||||
rvwStpComCd: z.string().nullish(),
|
||||
selected: z.enum(["Y", "N"]).nullish(),
|
||||
picRvwCn: z.string().nullish(),
|
||||
});
|
||||
|
||||
/** 백엔드 FnrsCnsltnApplySaveRequestDto.EsntlAtchFileItem */
|
||||
export const fnrsCnsltnEsntlAtchFileItemSchema = z.object({
|
||||
esntlAtchFileId: z.string().optional(),
|
||||
atchFileGroupId: z.string().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 FnrsCnsltnApplySaveRequestDto.FnrsCnsltnSplmntDmndItem */
|
||||
export const fnrsCnsltnSplmntDmndItemSchema = z.object({
|
||||
splmntDmndSn: z.number().nullish(),
|
||||
splmntArtclComCd: z.string().nullish(),
|
||||
splmntDmndCn: z.string().optional(),
|
||||
splmntDmndDt: z.string().optional(),
|
||||
splmntDmndAtchFileGroupId: z.string().optional(),
|
||||
splmntCn: z.string().optional(),
|
||||
splmntPicNm: z.string().optional(),
|
||||
splmntCmptnDt: z.string().optional(),
|
||||
splmntAtchFileGroupId: z.string().optional(),
|
||||
cmptnYn: z.string().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 FnrsCnsltnRvwOpnnSaveRequestDto */
|
||||
export const fnrsCnsltnRvwOpnnSaveSchema = z.object({
|
||||
fnrsCnsltnDocNo: z.string(),
|
||||
rvwOpnnSn: z.number().optional(),
|
||||
opnrSeComCd: z.string().optional(),
|
||||
opnrNm: z.string().optional(),
|
||||
rvwRsltCn: z.string().optional(),
|
||||
rvwRsltAnmlyYn: z.string().optional(),
|
||||
atchFileGroupId: z.string().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 FnrsCnsltnApplySaveRequestDto.FnrsSharItem */
|
||||
export const fnrsSharItemSchema = z.object({
|
||||
fnrsSharSeCd: z.string().optional(),
|
||||
exstnAmt: z.number().optional(),
|
||||
chgRqtAmt: z.number().optional(),
|
||||
ajmtAmt: z.number().optional(),
|
||||
rmrkCn: z.string().optional(),
|
||||
envoRmrkCn: z.string().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 FnrsCnsltnApplySaveRequestDto */
|
||||
export const fnrsCnsltnApplySaveSchema = z.object({
|
||||
techReViewSaveCd: z.string().optional(),
|
||||
rvwStpCd: z.string().optional(),
|
||||
fnrsCnsltnMng: fnrsCnsltnMngSaveSchema,
|
||||
bzScaleList: z.array(fnrsCnsltnBzScaleItemSchema).optional(),
|
||||
bizCstList: z.array(fnrsCnsltnBizCstItemSchema).optional(),
|
||||
cklstResultList: z.array(fnrsCnsltnCklstResultItemSchema).optional(),
|
||||
cklstResultMap: z.record(z.array(fnrsCnsltnCklstResultItemSchema)).optional(),
|
||||
esntlAtchFileList: z.array(fnrsCnsltnEsntlAtchFileItemSchema).optional(),
|
||||
rvwOpnnList: z.array(fnrsCnsltnRvwOpnnSaveSchema).optional(),
|
||||
splmntDmndList: z.array(fnrsCnsltnSplmntDmndItemSchema).optional(),
|
||||
fnrsSharList: z.array(fnrsSharItemSchema).optional(),
|
||||
});
|
||||
|
||||
export type FnrsCnsltnMngSaveRequestDto = z.infer<typeof fnrsCnsltnMngSaveSchema>;
|
||||
export type BzScaleItem = z.infer<typeof fnrsCnsltnBzScaleItemSchema>;
|
||||
export type BizCstItem = z.infer<typeof fnrsCnsltnBizCstItemSchema>;
|
||||
export type CklstResultItem = z.infer<typeof fnrsCnsltnCklstResultItemSchema>;
|
||||
export type EsntlAtchFileItem = z.infer<typeof fnrsCnsltnEsntlAtchFileItemSchema>;
|
||||
export type FnrsCnsltnSplmntDmndItem = z.infer<typeof fnrsCnsltnSplmntDmndItemSchema>;
|
||||
export type FnrsCnsltnRvwOpnnSaveRequestDto = z.infer<typeof fnrsCnsltnRvwOpnnSaveSchema>;
|
||||
export type FnrsSharItem = z.infer<typeof fnrsSharItemSchema>;
|
||||
export type FnrsCnsltnApplySaveRequestDto = z.infer<typeof fnrsCnsltnApplySaveSchema>;
|
||||
@@ -1,21 +1,20 @@
|
||||
import type {
|
||||
AtflMngClientResponseDto,
|
||||
BizCstSaveBase,
|
||||
BzScaleSaveBase,
|
||||
CklstResultSaveBase,
|
||||
DocPrgrsUpdateRequestDto,
|
||||
EsntlAtchFileSaveBase,
|
||||
PageableParams,
|
||||
PageResponse,
|
||||
PbadmsApplySaveHeader,
|
||||
PbadmsMngDetResponseBase,
|
||||
RvwOpnnResponseExtension,
|
||||
RvwOpnnSaveBase,
|
||||
SplmntDmndResponseExtension,
|
||||
SplmntDmndSaveBase,
|
||||
} from "@/shared/model";
|
||||
import type { FnrsSharBizCstTreeDto } from "@/entities/fnrs-shar-se";
|
||||
|
||||
import type {
|
||||
FnrsCnsltnApplySaveRequestDto,
|
||||
FnrsCnsltnMngSaveRequestDto,
|
||||
FnrsCnsltnRvwOpnnSaveRequestDto,
|
||||
FnrsCnsltnSplmntDmndItem,
|
||||
} from "./fnrs-cnsltn-apply-payload-schema";
|
||||
import type {
|
||||
EnvoActnReviewReqDto,
|
||||
FnrsExamineRequestDto,
|
||||
@@ -31,33 +30,6 @@ export type {
|
||||
EsntlAtchFileMngTreeResponseDto,
|
||||
} from "@/shared/model";
|
||||
|
||||
/** 백엔드 FnrsCnsltnMngSaveRequestDto */
|
||||
export interface FnrsCnsltnMngSaveRequestDto {
|
||||
fnrsCnsltnDocNo?: string;
|
||||
admdstCd?: string;
|
||||
envoSeComCd?: string;
|
||||
taskSeComCd?: string;
|
||||
cfmtnBgtBizNo?: string;
|
||||
bgtAplyYr?: string;
|
||||
bizNm?: string;
|
||||
bizBgngYr?: string;
|
||||
bizEndYr?: string;
|
||||
bizCn?: string;
|
||||
aplcntNm?: string;
|
||||
aplcntMblTelno?: string;
|
||||
aplcntOgdpNm?: string;
|
||||
aplcntEmlAddr?: string;
|
||||
dsgnrNm?: string;
|
||||
dsgnrTelno?: string;
|
||||
dsgnrOgdpNm?: string;
|
||||
dsgnrEmlAddr?: string;
|
||||
atchFileGroupId?: string;
|
||||
sharAtchFileGroupId?: string;
|
||||
prgrsSttsComCd?: string;
|
||||
rtlztnFcltSeComCd?: string;
|
||||
rtlztnUsgSeComCd?: string;
|
||||
}
|
||||
|
||||
/** 백엔드 FnrsCnsltnMngUpdateRequestDto */
|
||||
export type FnrsCnsltnMngUpdateRequestDto = Omit<FnrsCnsltnMngSaveRequestDto, "fnrsCnsltnDocNo">;
|
||||
|
||||
@@ -110,34 +82,6 @@ interface FnrsCnsltnMngResponseExtension {
|
||||
export type FnrsCnsltnMngResponseDto = Omit<FnrsCnsltnMngSaveRequestDto, "fnrsCnsltnDocNo"> &
|
||||
FnrsCnsltnMngResponseExtension;
|
||||
|
||||
export type BzScaleItem = BzScaleSaveBase;
|
||||
|
||||
export type BizCstItem = BizCstSaveBase;
|
||||
|
||||
export interface CklstResultItem extends CklstResultSaveBase {
|
||||
selected?: string;
|
||||
}
|
||||
|
||||
export type EsntlAtchFileItem = EsntlAtchFileSaveBase;
|
||||
|
||||
export type FnrsCnsltnSplmntDmndItem = SplmntDmndSaveBase;
|
||||
|
||||
/** 백엔드 FnrsCnsltnApplySaveRequestDto.FnrsSharItem */
|
||||
export interface FnrsSharItem {
|
||||
fnrsSharSeCd?: string;
|
||||
exstnAmt?: number;
|
||||
chgRqtAmt?: number;
|
||||
ajmtAmt?: number;
|
||||
rmrkCn?: string;
|
||||
envoRmrkCn?: string;
|
||||
}
|
||||
|
||||
/** 백엔드 FnrsCnsltnRvwOpnnSaveRequestDto */
|
||||
export interface FnrsCnsltnRvwOpnnSaveRequestDto extends RvwOpnnSaveBase {
|
||||
fnrsCnsltnDocNo: string;
|
||||
rvwOpnnSn: number;
|
||||
}
|
||||
|
||||
/** 백엔드 FnrsCnsltnRvwOpnnResponseDto */
|
||||
export type FnrsCnsltnRvwOpnnResponseDto = Partial<
|
||||
Pick<FnrsCnsltnRvwOpnnSaveRequestDto, "fnrsCnsltnDocNo" | "rvwOpnnSn">
|
||||
@@ -151,6 +95,14 @@ export type FnrsCnsltnSplmntDmndResponseDto = FnrsCnsltnSplmntDmndItem &
|
||||
fnrsCnsltnDocNo?: string;
|
||||
};
|
||||
|
||||
/** 백엔드 RagIndexingInfoDto */
|
||||
export interface RagIndexingInfoDto {
|
||||
isInProgress: boolean;
|
||||
elapsedSeconds?: number;
|
||||
estimatedRemainingSeconds?: number;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
/** 백엔드 FnrsCnsltnMngDetResponseDto */
|
||||
export type FnrsCnsltnMngDetResponseDto = PbadmsMngDetResponseBase<
|
||||
FnrsCnsltnMngResponseDto,
|
||||
@@ -159,21 +111,9 @@ export type FnrsCnsltnMngDetResponseDto = PbadmsMngDetResponseBase<
|
||||
> & {
|
||||
fnrsSharList?: FnrsSharBizCstTreeDto[];
|
||||
sharAtchFileList?: AtflMngClientResponseDto[];
|
||||
ragIndexingInfo?: RagIndexingInfoDto | null;
|
||||
};
|
||||
|
||||
/** 백엔드 FnrsCnsltnApplySaveRequestDto */
|
||||
export interface FnrsCnsltnApplySaveRequestDto extends PbadmsApplySaveHeader {
|
||||
fnrsCnsltnMng: FnrsCnsltnMngSaveRequestDto;
|
||||
bzScaleList?: BzScaleItem[];
|
||||
bizCstList?: BizCstItem[];
|
||||
cklstResultList?: CklstResultItem[];
|
||||
cklstResultMap?: Record<string, CklstResultItem[]>;
|
||||
esntlAtchFileList?: EsntlAtchFileItem[];
|
||||
rvwOpnnList?: FnrsCnsltnRvwOpnnSaveRequestDto[];
|
||||
splmntDmndList?: FnrsCnsltnSplmntDmndItem[];
|
||||
fnrsSharList?: FnrsSharItem[];
|
||||
}
|
||||
|
||||
/** 백엔드 FnrsCnsltnFileVrfcRsltResponseDto */
|
||||
export interface FnrsCnsltnFileVrfcRsltResponseDto {
|
||||
fileSeComCd?: string;
|
||||
|
||||
@@ -2,13 +2,15 @@ import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import type { PageResponse } from "@/shared/model";
|
||||
|
||||
import type {
|
||||
InstlSncnApplySaveRequestDto,
|
||||
InstlSncnMngSaveRequestDto,
|
||||
} from "../model/instl-sncn-apply-payload-schema";
|
||||
import type {
|
||||
AdstZoneResponseDto,
|
||||
DocPrgrsUpdateRequestDto,
|
||||
InstlSncnApplySaveRequestDto,
|
||||
InstlSncnMngDetResponse,
|
||||
InstlSncnMngResponseDto,
|
||||
InstlSncnMngSaveRequestDto,
|
||||
InstlSncnMngSearchParams,
|
||||
InstlSncnMngService,
|
||||
InstlSncnMngUpdateRequestDto,
|
||||
|
||||
@@ -3,9 +3,11 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
|
||||
import type {
|
||||
DocPrgrsUpdateRequestDto,
|
||||
InstlSncnApplySaveRequestDto,
|
||||
InstlSncnMngSaveRequestDto,
|
||||
} from "../model/instl-sncn-apply-payload-schema";
|
||||
import type {
|
||||
DocPrgrsUpdateRequestDto,
|
||||
InstlSncnMngSearchParams,
|
||||
InstlSncnMngUpdateRequestDto,
|
||||
} from "../model/instl-sncn-interface";
|
||||
|
||||
@@ -9,6 +9,26 @@ export {
|
||||
useInstlSncnSubmitSupplement,
|
||||
useInstlSncnTechReview,
|
||||
} from "./api/queries";
|
||||
export type {
|
||||
InstlSncnApplySaveRequestDto,
|
||||
InstlSncnBizCstSaveRequestDto,
|
||||
InstlSncnBzScaleSaveDto,
|
||||
InstlSncnCklstResultSaveDto,
|
||||
InstlSncnEsntlAtchFileSaveDto,
|
||||
InstlSncnMngSaveRequestDto,
|
||||
InstlSncnRvwOpnnSaveRequestDto,
|
||||
InstlSncnSplmntDmndSaveDto,
|
||||
} from "./model/instl-sncn-apply-payload-schema";
|
||||
export {
|
||||
instlSncnApplySaveSchema,
|
||||
instlSncnBizCstItemSchema,
|
||||
instlSncnBzScaleItemSchema,
|
||||
instlSncnCklstResultItemSchema,
|
||||
instlSncnEsntlAtchFileItemSchema,
|
||||
instlSncnMngSaveSchema,
|
||||
instlSncnRvwOpnnSaveSchema,
|
||||
instlSncnSplmntDmndItemSchema,
|
||||
} from "./model/instl-sncn-apply-payload-schema";
|
||||
export {
|
||||
type AdstZoneResponseDto,
|
||||
type AtflMngClientResponseDto,
|
||||
@@ -19,18 +39,10 @@ export {
|
||||
type ElaprDocPrgrsClientResponseDto,
|
||||
type EsntlAtchFileMngTreeResponseDto,
|
||||
INSTL_SNCN_REGISTER_SELECTION_KEY,
|
||||
type InstlSncnApplySaveRequestDto,
|
||||
type InstlSncnBizCstSaveRequestDto,
|
||||
type InstlSncnBzScaleSaveDto,
|
||||
type InstlSncnCklstResultSaveDto,
|
||||
type InstlSncnEsntlAtchFileSaveDto,
|
||||
type InstlSncnMngDetResponse,
|
||||
type InstlSncnMngResponseDto,
|
||||
type InstlSncnMngSaveRequestDto,
|
||||
type InstlSncnMngSearchParams,
|
||||
type InstlSncnRvwOpnnResponseDto,
|
||||
type InstlSncnRvwOpnnSaveRequestDto,
|
||||
type InstlSncnSplmntDmndResponseDto,
|
||||
type InstlSncnSplmntDmndSaveDto,
|
||||
type ReviewStepCode,
|
||||
} from "./model/instl-sncn-interface";
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
import { z } from "zod";
|
||||
|
||||
/** 백엔드 InstlSncnMngSaveRequestDto */
|
||||
export const instlSncnMngSaveSchema = z.object({
|
||||
instlSncnDocNo: z.string().optional(),
|
||||
admdstCd: z.string().optional(),
|
||||
envoSeComCd: z.string().optional(),
|
||||
sncnSeComCd: z.string().optional(),
|
||||
taskSeComCd: z.string().optional(),
|
||||
bgtBizCd: z.string().optional(),
|
||||
bgtAplyYr: z.string().optional(),
|
||||
bizNm: z.string().optional(),
|
||||
bizBgngYr: z.string().optional(),
|
||||
bizEndYr: z.string().optional(),
|
||||
bizCn: z.string().optional(),
|
||||
aplcntNm: z.string().optional(),
|
||||
aplcntMblTelno: z.string().optional(),
|
||||
aplcntOgdpNm: z.string().optional(),
|
||||
aplcntEmlAddr: z.string().optional(),
|
||||
dsgnrNm: z.string().optional(),
|
||||
dsgnrTelno: z.string().optional(),
|
||||
dsgnrOgdpNm: z.string().optional(),
|
||||
dsgnrEmlAddr: z.string().optional(),
|
||||
atchFileGroupId: z.string().optional(),
|
||||
prgrsSttsComCd: z.string().optional(),
|
||||
rtlztnFcltSeComCd: z.string().optional(),
|
||||
rtlztnUsgSeComCd: z.string().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 InstlSncnApplySaveRequestDto.BzScaleItem (diff는 API 미전송) */
|
||||
export const instlSncnBzScaleItemSchema = z.object({
|
||||
cnstpSeCd: z.string().optional(),
|
||||
bscPl: z.number().optional(),
|
||||
budgetReflct: z.number().optional(),
|
||||
thtiDsgn: z.number().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 InstlSncnApplySaveRequestDto.BizCstItem */
|
||||
export const instlSncnBizCstItemSchema = z.object({
|
||||
acntgSeCd: z.string().optional(),
|
||||
bscPlanAmt: z.number().optional(),
|
||||
bgtRfltAmt: z.number().optional(),
|
||||
tstmDesignAmt: z.number().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 InstlSncnApplySaveRequestDto.CklstResultItem */
|
||||
export const instlSncnCklstResultItemSchema = z.object({
|
||||
cklstRvwSn: z.number().optional(),
|
||||
cklstArtclSn: z.number().optional(),
|
||||
cklstSn: z.number().optional(),
|
||||
rvwStpComCd: z.string().nullish(),
|
||||
selected: z.enum(["Y", "N"]).nullish(),
|
||||
picRvwCn: z.string().nullish(),
|
||||
});
|
||||
|
||||
/** 백엔드 InstlSncnApplySaveRequestDto.EsntlAtchFileItem */
|
||||
export const instlSncnEsntlAtchFileItemSchema = z.object({
|
||||
esntlAtchFileId: z.string().optional(),
|
||||
atchFileGroupId: z.string().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 InstlSncnRvwOpnnSaveRequestDto */
|
||||
export const instlSncnRvwOpnnSaveSchema = z.object({
|
||||
instlSncnDocNo: z.string(),
|
||||
rvwOpnnSn: z.number().optional(),
|
||||
opnrSeComCd: z.string().optional(),
|
||||
opnrNm: z.string().optional(),
|
||||
rvwRsltCn: z.string().optional(),
|
||||
rvwRsltAnmlyYn: z.string().optional(),
|
||||
atchFileGroupId: z.string().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 InstlSncnApplySaveRequestDto.InstlSncnSplmntDmndItem */
|
||||
export const instlSncnSplmntDmndItemSchema = z.object({
|
||||
splmntDmndSn: z.number().nullish(),
|
||||
splmntArtclComCd: z.string().nullish(),
|
||||
splmntDmndCn: z.string().optional(),
|
||||
splmntDmndDt: z.string().optional(),
|
||||
splmntDmndAtchFileGroupId: z.string().optional(),
|
||||
splmntCn: z.string().optional(),
|
||||
splmntPicNm: z.string().optional(),
|
||||
splmntCmptnDt: z.string().optional(),
|
||||
splmntAtchFileGroupId: z.string().optional(),
|
||||
cmptnYn: z.string().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 InstlSncnApplySaveRequestDto */
|
||||
export const instlSncnApplySaveSchema = z.object({
|
||||
techReViewSaveCd: z.string().optional(),
|
||||
rvwStpCd: z.string().optional(),
|
||||
instlSncnMng: instlSncnMngSaveSchema,
|
||||
bzScaleList: z.array(instlSncnBzScaleItemSchema).optional(),
|
||||
bizCstList: z.array(instlSncnBizCstItemSchema).optional(),
|
||||
cklstResultList: z.array(instlSncnCklstResultItemSchema).optional(),
|
||||
cklstResultMap: z.record(z.array(instlSncnCklstResultItemSchema)).optional(),
|
||||
esntlAtchFileList: z.array(instlSncnEsntlAtchFileItemSchema).optional(),
|
||||
rvwOpnnList: z.array(instlSncnRvwOpnnSaveSchema).optional(),
|
||||
splmntDmndList: z.array(instlSncnSplmntDmndItemSchema).optional(),
|
||||
});
|
||||
|
||||
export type InstlSncnMngSaveRequestDto = z.infer<typeof instlSncnMngSaveSchema>;
|
||||
export type InstlSncnBzScaleSaveDto = z.infer<typeof instlSncnBzScaleItemSchema>;
|
||||
export type InstlSncnBizCstSaveRequestDto = z.infer<typeof instlSncnBizCstItemSchema>;
|
||||
export type InstlSncnCklstResultSaveDto = z.infer<typeof instlSncnCklstResultItemSchema>;
|
||||
export type InstlSncnEsntlAtchFileSaveDto = z.infer<typeof instlSncnEsntlAtchFileItemSchema>;
|
||||
export type InstlSncnRvwOpnnSaveRequestDto = z.infer<typeof instlSncnRvwOpnnSaveSchema>;
|
||||
export type InstlSncnSplmntDmndSaveDto = z.infer<typeof instlSncnSplmntDmndItemSchema>;
|
||||
export type InstlSncnApplySaveRequestDto = z.infer<typeof instlSncnApplySaveSchema>;
|
||||
@@ -1,24 +1,24 @@
|
||||
import type {
|
||||
AtflMngClientResponseDto,
|
||||
BizCstSaveBase,
|
||||
BizCstTreeDto,
|
||||
BizSclTreeDto,
|
||||
BzScaleSaveBase,
|
||||
CklstResultSaveBase,
|
||||
DocPrgrsUpdateRequestDto,
|
||||
ElaprDocPrgrsClientResponseDto,
|
||||
EsntlAtchFileMngTreeResponseDto,
|
||||
EsntlAtchFileSaveBase,
|
||||
PageableParams,
|
||||
PageResponse,
|
||||
PbadmsApplySaveHeader,
|
||||
PbadmsMngDetResponseBase,
|
||||
RvwOpnnResponseExtension,
|
||||
RvwOpnnSaveBase,
|
||||
SplmntDmndResponseExtension,
|
||||
SplmntDmndSaveBase,
|
||||
} from "@/shared/model";
|
||||
|
||||
import type {
|
||||
InstlSncnApplySaveRequestDto,
|
||||
InstlSncnMngSaveRequestDto,
|
||||
InstlSncnRvwOpnnSaveRequestDto,
|
||||
InstlSncnSplmntDmndSaveDto,
|
||||
} from "./instl-sncn-apply-payload-schema";
|
||||
|
||||
export type {
|
||||
AtflMngClientResponseDto,
|
||||
BizCstTreeDto,
|
||||
@@ -41,189 +41,48 @@ export const INSTL_SNCN_REGISTER_SELECTION_KEY = "instlSncnRegisterSelection";
|
||||
|
||||
/** 백엔드 InstlSncnMngSearchDto + Pageable */
|
||||
export interface InstlSncnMngSearchParams extends PageableParams {
|
||||
/** 설치인가문서번호 */
|
||||
instlSncnDocNo?: string;
|
||||
/** 행정구역코드 */
|
||||
admdstCd?: string;
|
||||
/** 환경청구분공통코드 */
|
||||
envoSeComCd?: string;
|
||||
/** 업무구분공통코드 */
|
||||
taskSeComCd?: string;
|
||||
/** 예산사업코드 */
|
||||
bgtBizCd?: string;
|
||||
/** 예산신청연도 */
|
||||
bgtAplyYr?: string;
|
||||
/** 사업명 */
|
||||
bizNm?: string;
|
||||
/** 사업시작연도 */
|
||||
bizBgngYr?: string;
|
||||
/** 사업종료연도 */
|
||||
bizEndYr?: string;
|
||||
/** 사업내용 */
|
||||
bizCn?: string;
|
||||
/** 신청자명 */
|
||||
aplcntNm?: string;
|
||||
/** 신청자휴대전화번호 */
|
||||
aplcntMblTelno?: string;
|
||||
/** 신청자소속명 */
|
||||
aplcntOgdpNm?: string;
|
||||
/** 신청자이메일주소 */
|
||||
aplcntEmlAddr?: string;
|
||||
/** 설계자명 */
|
||||
dsgnrNm?: string;
|
||||
/** 설계자전화번호 */
|
||||
dsgnrTelno?: string;
|
||||
/** 설계자소속명 */
|
||||
dsgnrOgdpNm?: string;
|
||||
/** 설계자이메일주소 */
|
||||
dsgnrEmlAddr?: string;
|
||||
/** 첨부파일그룹아이디 */
|
||||
atchFileGroupId?: string;
|
||||
/** 진행상태공통코드 */
|
||||
prgrsSttsComCd?: string;
|
||||
}
|
||||
|
||||
/** 설치인가관리 저장 요청 DTO */
|
||||
export interface InstlSncnMngSaveRequestDto {
|
||||
/** 설치인가문서번호 */
|
||||
instlSncnDocNo?: string;
|
||||
/** 행정구역코드 */
|
||||
admdstCd?: string;
|
||||
/** 환경청구분공통코드 */
|
||||
envoSeComCd?: string;
|
||||
/** 인가구분공통코드 */
|
||||
sncnSeComCd: string;
|
||||
/** 업무구분공통코드 */
|
||||
taskSeComCd: string;
|
||||
/** 예산사업코드 */
|
||||
bgtBizCd?: string;
|
||||
/** 예산신청연도 */
|
||||
bgtAplyYr?: string;
|
||||
/** 사업명 */
|
||||
bizNm: string;
|
||||
/** 사업시작연도 */
|
||||
bizBgngYr: string;
|
||||
/** 사업종료연도 */
|
||||
bizEndYr: string;
|
||||
/** 사업내용 */
|
||||
bizCn: string;
|
||||
/** 신청자명 */
|
||||
aplcntNm?: string;
|
||||
/** 신청자휴대전화번호 */
|
||||
aplcntMblTelno?: string;
|
||||
/** 신청자소속명 */
|
||||
aplcntOgdpNm?: string;
|
||||
/** 신청자이메일주소 */
|
||||
aplcntEmlAddr?: string;
|
||||
/** 설계자명 */
|
||||
dsgnrNm: string;
|
||||
/** 설계자전화번호 */
|
||||
dsgnrTelno: string;
|
||||
/** 설계자소속명 */
|
||||
dsgnrOgdpNm: string;
|
||||
/** 설계자이메일주소 */
|
||||
dsgnrEmlAddr: string;
|
||||
/** 첨부파일그룹아이디 */
|
||||
atchFileGroupId?: string;
|
||||
/** 진행상태공통코드 */
|
||||
prgrsSttsComCd?: string;
|
||||
/** 재이용시설구분공통코드 */
|
||||
rtlztnFcltSeComCd?: string;
|
||||
/** 재이용용도구분공통코드 */
|
||||
rtlztnUsgSeComCd?: string;
|
||||
}
|
||||
|
||||
/** 설치인가관리 수정 요청 DTO */
|
||||
/** 백엔드 InstlSncnMngUpdateRequestDto */
|
||||
export type InstlSncnMngUpdateRequestDto = InstlSncnMngSaveRequestDto;
|
||||
|
||||
/** 설치인가관리 응답 DTO */
|
||||
export interface InstlSncnMngResponseDto extends InstlSncnMngSaveRequestDto {
|
||||
/** 설치인가문서번호 (필수로 재정의) */
|
||||
/** 백엔드 InstlSncnMngResponseDto */
|
||||
export type InstlSncnMngResponseDto = InstlSncnMngSaveRequestDto & {
|
||||
instlSncnDocNo: string;
|
||||
/** 환경청구분공통코드명 */
|
||||
envoSeCdNm: string;
|
||||
/** 인가구분공통코드명 */
|
||||
sncnSeCdNm: string;
|
||||
/** 업무구분공통코드명 */
|
||||
taskSeCdNm: string;
|
||||
/** 진행상태공통코드명 */
|
||||
prgrsSttsCdNm: string;
|
||||
/** 기술검토회신이력여부 */
|
||||
techRplyHstryYn: "Y" | "N";
|
||||
/** 재이용시설구분공통코드명 */
|
||||
rtlztnFcltSeComNm: string;
|
||||
/** 재이용용도구분공통코드명 */
|
||||
rtlztnUsgSeComNm: string;
|
||||
/** 등록일시 */
|
||||
regDt: string;
|
||||
/** 등록자 UUID */
|
||||
rgtr: string;
|
||||
/** 등록자명 */
|
||||
rgtrNm: string;
|
||||
/** 수정 일시 */
|
||||
mdfcnDt: string;
|
||||
/** 시도 */
|
||||
sido: string;
|
||||
/** 구군 */
|
||||
gugun: string;
|
||||
}
|
||||
|
||||
/** 설치인가 신청 - 사업규모 저장 DTO(배열 원소) */
|
||||
export interface InstlSncnBzScaleSaveDto extends BzScaleSaveBase {
|
||||
/** 공종구분코드 */
|
||||
cnstpSeCd: string;
|
||||
/** 기본계획수량 */
|
||||
bscPl: number;
|
||||
/** 예산반영수량 */
|
||||
budgetReflct: number;
|
||||
/** 금회설계수량 */
|
||||
thtiDsgn: number;
|
||||
/** 증감(C-B) — 클라이언트 계산용, API 미전송 */
|
||||
diff?: number;
|
||||
}
|
||||
|
||||
/** 설치인가 신청 - 총사업비 저장 DTO(배열 원소) */
|
||||
export interface InstlSncnBizCstSaveRequestDto extends BizCstSaveBase {
|
||||
/** 회계구분코드 */
|
||||
acntgSeCd: string;
|
||||
/** 기본계획금액 */
|
||||
bscPlanAmt: number;
|
||||
/** 예산반영금액 */
|
||||
bgtRfltAmt: number;
|
||||
/** 금회설계금액 */
|
||||
tstmDesignAmt: number;
|
||||
}
|
||||
|
||||
/** 설치인가 신청 - 체크리스트 결과 저장 DTO(배열 원소) */
|
||||
export interface InstlSncnCklstResultSaveDto extends CklstResultSaveBase {
|
||||
/** 점검표일련번호 */
|
||||
cklstSn: number;
|
||||
/** 점검표항목일련번호 */
|
||||
cklstArtclSn: number;
|
||||
/** 점검표검토일련번호 */
|
||||
cklstRvwSn: number;
|
||||
/** 선택값(Y/N/null) */
|
||||
selected: "Y" | "N" | null;
|
||||
/** 담당자검토내용(기술검토의견) */
|
||||
picRvwCn?: string | null;
|
||||
}
|
||||
|
||||
/** 설치인가 신청 - 필수첨부파일 저장 DTO(배열 원소) */
|
||||
export interface InstlSncnEsntlAtchFileSaveDto extends EsntlAtchFileSaveBase {
|
||||
/** 필수첨부파일아이디 */
|
||||
esntlAtchFileId: string;
|
||||
/** 첨부파일그룹아이디 */
|
||||
atchFileGroupId: string;
|
||||
}
|
||||
|
||||
/** 설치인가검토의견 저장 요청 DTO */
|
||||
export interface InstlSncnRvwOpnnSaveRequestDto extends RvwOpnnSaveBase {
|
||||
/** 설치인가문서번호 */
|
||||
instlSncnDocNo: string;
|
||||
}
|
||||
|
||||
/** 설치인가 보완요구 저장 DTO(배열 원소) */
|
||||
export type InstlSncnSplmntDmndSaveDto = SplmntDmndSaveBase;
|
||||
};
|
||||
|
||||
/** 설치인가 보완요구 응답 DTO */
|
||||
export type InstlSncnSplmntDmndResponseDto = InstlSncnSplmntDmndSaveDto &
|
||||
@@ -231,26 +90,6 @@ export type InstlSncnSplmntDmndResponseDto = InstlSncnSplmntDmndSaveDto &
|
||||
instlSncnDocNo?: string;
|
||||
};
|
||||
|
||||
/** 설치인가 신청 - 통합 저장 요청 DTO */
|
||||
export interface InstlSncnApplySaveRequestDto extends PbadmsApplySaveHeader {
|
||||
/** 설치인가관리 정보 */
|
||||
instlSncnMng: InstlSncnMngSaveRequestDto;
|
||||
/** 사업규모 목록 */
|
||||
bzScaleList?: InstlSncnBzScaleSaveDto[];
|
||||
/** 총사업비 목록 */
|
||||
bizCstList?: InstlSncnBizCstSaveRequestDto[];
|
||||
/** 체크리스트 검토결과 목록 */
|
||||
cklstResultList?: InstlSncnCklstResultSaveDto[];
|
||||
/** 체크리스트 검토결과 맵 (rvwStpComCd -> 목록) */
|
||||
cklstResultMap?: Record<string, InstlSncnCklstResultSaveDto[]>;
|
||||
/** 필수첨부파일 목록 */
|
||||
esntlAtchFileList?: InstlSncnEsntlAtchFileSaveDto[];
|
||||
/** 검토의견 목록 */
|
||||
rvwOpnnList?: InstlSncnRvwOpnnSaveRequestDto[];
|
||||
/** 보완요구 목록 */
|
||||
splmntDmndList?: InstlSncnSplmntDmndSaveDto[];
|
||||
}
|
||||
|
||||
export type ReviewStepCode = "LOCAL" | "ENV" | "KECO";
|
||||
|
||||
/** 설치인가검토의견 응답 DTO */
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import type { PageResponse } from "@/shared/model";
|
||||
|
||||
import type {
|
||||
PwrkCnsltnDocPjtcoAjmtMngResponseDto,
|
||||
PwrkCnsltnDocPjtcoAjmtMngSearchDto,
|
||||
PwrkCnsltnDocPjtcoAjmtMngService,
|
||||
} from "../model/pwrk-cnsltn-doc-pjtco-ajmt-mng-interface";
|
||||
|
||||
const BASE = ENDPOINT.AS_SERVICE.PWRK_CNSLTN_DOC_PJTCO_AJMT_MNG;
|
||||
|
||||
async function findPage(
|
||||
searchDto: PwrkCnsltnDocPjtcoAjmtMngSearchDto,
|
||||
): Promise<PageResponse<PwrkCnsltnDocPjtcoAjmtMngResponseDto>> {
|
||||
const response = await client.get<PageResponse<PwrkCnsltnDocPjtcoAjmtMngResponseDto>>(BASE, {
|
||||
params: searchDto,
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export const pwrkCnsltnDocPjtcoAjmtMngService = {
|
||||
findPage,
|
||||
} satisfies PwrkCnsltnDocPjtcoAjmtMngService;
|
||||
@@ -0,0 +1,22 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import type { PwrkCnsltnDocPjtcoAjmtMngSearchDto } from "../model/pwrk-cnsltn-doc-pjtco-ajmt-mng-interface";
|
||||
|
||||
import { pwrkCnsltnDocPjtcoAjmtMngService } from "./pwrk-cnsltn-doc-pjtco-ajmt-mng-service";
|
||||
|
||||
export const pwrkCnsltnDocPjtcoAjmtMngKeys = {
|
||||
all: ["pwrk-cnsltn-doc-pjtco-ajmt-mng"] as const,
|
||||
page: (params: PwrkCnsltnDocPjtcoAjmtMngSearchDto) =>
|
||||
[...pwrkCnsltnDocPjtcoAjmtMngKeys.all, "findPage", params] as const,
|
||||
};
|
||||
|
||||
export function usePwrkCnsltnDocPjtcoAjmtMngPage(
|
||||
params: PwrkCnsltnDocPjtcoAjmtMngSearchDto,
|
||||
isEnabled = true,
|
||||
) {
|
||||
return useQuery({
|
||||
queryKey: pwrkCnsltnDocPjtcoAjmtMngKeys.page(params),
|
||||
queryFn: () => pwrkCnsltnDocPjtcoAjmtMngService.findPage(params),
|
||||
enabled: Boolean(params.pwrkCnsltnDocNo) && isEnabled,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export { pwrkCnsltnDocPjtcoAjmtMngService } from "./api/pwrk-cnsltn-doc-pjtco-ajmt-mng-service";
|
||||
export {
|
||||
pwrkCnsltnDocPjtcoAjmtMngKeys,
|
||||
usePwrkCnsltnDocPjtcoAjmtMngPage,
|
||||
} from "./api/queries";
|
||||
export type {
|
||||
PwrkCnsltnDocPjtcoAjmtMngResponseDto,
|
||||
PwrkCnsltnDocPjtcoAjmtMngSearchDto,
|
||||
PwrkCnsltnDocPjtcoAjmtMngService,
|
||||
} from "./model/pwrk-cnsltn-doc-pjtco-ajmt-mng-interface";
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import type { PageableParams, PageResponse } from "@/shared/model";
|
||||
|
||||
/** 백엔드 PwrkCnsltnDocPjtcoAjmtMngResponseDto */
|
||||
export interface PwrkCnsltnDocPjtcoAjmtMngResponseDto {
|
||||
pwrkCnsltnDocNo: string;
|
||||
rvwOpnnSn: number;
|
||||
acntgSeCd: string;
|
||||
acntgSeCdNm?: string;
|
||||
rvwOpnnClsfComCd?: string;
|
||||
rvwOpnnClsfComCdNm?: string;
|
||||
ognTpcoAmt?: number;
|
||||
ognGvexpAmt?: number;
|
||||
ognLcexpAmt?: number;
|
||||
ognOrgprBrdnAmt?: number;
|
||||
chgTpcoAmt?: number;
|
||||
chgGvexpAmt?: number;
|
||||
chgLcexpAmt?: number;
|
||||
chgOrgprBrdnAmt?: number;
|
||||
ajmtRsn?: string;
|
||||
vrfcTpcoAmt?: number;
|
||||
vrfcGvexpAmt?: number;
|
||||
vrfcLcexpAmt?: number;
|
||||
vrfcOrgprBrdnAmt?: number;
|
||||
rsbnYn?: string;
|
||||
vrfcCn?: string;
|
||||
regDt?: string;
|
||||
rgtr?: string;
|
||||
rgtrNm?: string;
|
||||
mdfcnDt?: string;
|
||||
mdfr?: string;
|
||||
}
|
||||
|
||||
/** 백엔드 PwrkCnsltnDocPjtcoAjmtMngSearchDto + Pageable */
|
||||
export interface PwrkCnsltnDocPjtcoAjmtMngSearchDto extends PageableParams {
|
||||
pwrkCnsltnDocNo?: string;
|
||||
rvwOpnnSn?: number;
|
||||
acntgSeCd?: string;
|
||||
}
|
||||
|
||||
export interface PwrkCnsltnDocPjtcoAjmtMngService {
|
||||
findPage(
|
||||
searchDto: PwrkCnsltnDocPjtcoAjmtMngSearchDto,
|
||||
): Promise<PageResponse<PwrkCnsltnDocPjtcoAjmtMngResponseDto>>;
|
||||
}
|
||||
@@ -47,7 +47,7 @@ export const sewerBscPlanCklstResultItemSchema = z.object({
|
||||
cklstArtclSn: z.number().optional(),
|
||||
cklstSn: z.number().optional(),
|
||||
rvwStpComCd: z.string(),
|
||||
selected: z.string().optional(),
|
||||
selected: z.enum(["Y", "N"]).nullish(),
|
||||
picRvwCn: z.string().nullish(),
|
||||
});
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@ function ElaprAplnGrid({ elaprTmpltId }: ElaprAplnGridProps) {
|
||||
markDeleted,
|
||||
reorderRows,
|
||||
} = useAplnRows({
|
||||
seedKey: elaprTmpltId,
|
||||
elaprTmpltId,
|
||||
serverRows: aplnQuery.data?.content,
|
||||
});
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
designCnstnBzScaleItemSchema,
|
||||
designCnstnEsntlAtchFileItemSchema,
|
||||
designCnstnMngSaveSchema,
|
||||
} from "@/entities/design-cnstn";
|
||||
|
||||
export const designCnstnRegistSchema = z.object({
|
||||
designCnstnMng: z.object({
|
||||
designCnstnMng: designCnstnMngSaveSchema.extend({
|
||||
bizNm: z.string().min(1, "사업명은 필수입니다"),
|
||||
bizBgngYr: z.string().min(1, "사업시작연도는 필수입니다"),
|
||||
bizEndYr: z.string().min(1, "사업종료연도는 필수입니다"),
|
||||
@@ -17,27 +23,11 @@ export const designCnstnRegistSchema = z.object({
|
||||
.string()
|
||||
.min(1, "설계자 이메일은 필수입니다")
|
||||
.email("올바른 이메일 형식이 아닙니다"),
|
||||
aplcntNm: z.string().optional(),
|
||||
aplcntMblTelno: z.string().optional(),
|
||||
aplcntOgdpNm: z.string().optional(),
|
||||
aplcntEmlAddr: z.string().optional(),
|
||||
designCnstnDocNo: z.string().optional(),
|
||||
admdstCd: z.string().optional(),
|
||||
envoSeComCd: z.string().optional(),
|
||||
prgrsSttsComCd: z.string().optional(),
|
||||
atchFileGroupId: z.string().optional(),
|
||||
envoSeCdNm: z.string().optional(),
|
||||
taskSeCdNm: z.string().optional(),
|
||||
prgrsSttsCdNm: z.string().optional(),
|
||||
sido: z.string().optional(),
|
||||
gugun: z.string().optional(),
|
||||
regDt: z.string().optional(),
|
||||
rgtr: z.string().optional(),
|
||||
rgtrNm: z.string().optional(),
|
||||
mdfcnDt: z.string().optional(),
|
||||
}),
|
||||
bzScaleList: z.array(
|
||||
z.object({
|
||||
designCnstnBzScaleItemSchema.extend({
|
||||
cnstpSeCd: z.string(),
|
||||
bscPl: z.number(),
|
||||
budgetReflct: z.number(),
|
||||
@@ -46,7 +36,7 @@ export const designCnstnRegistSchema = z.object({
|
||||
}),
|
||||
),
|
||||
esntlAtchFileList: z.array(
|
||||
z.object({
|
||||
designCnstnEsntlAtchFileItemSchema.extend({
|
||||
esntlAtchFileId: z.string(),
|
||||
atchFileGroupId: z.string(),
|
||||
}),
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import type { IUserInfo } from "@/shared/store";
|
||||
import type { DesignCnstnMngDetResponseDto } from "@/entities/design-cnstn";
|
||||
import {
|
||||
type DesignCnstnApplySaveRequestDto,
|
||||
designCnstnApplySaveSchema,
|
||||
type DesignCnstnMngDetResponseDto,
|
||||
} from "@/entities/design-cnstn";
|
||||
import { toEsntlAtchFileSaveList } from "@/widgets/pbadms-sprt";
|
||||
|
||||
import type { DesignCnstnRegistFormData } from "./design-cnstn-regist-schema";
|
||||
import { parseTaskSeComCdFromApi } from "./design-cnstn-regist-schema";
|
||||
import { formatTaskSeComCdForApi, parseTaskSeComCdFromApi } from "./design-cnstn-regist-schema";
|
||||
|
||||
export function mapDetailToFormData(data: DesignCnstnMngDetResponseDto): DesignCnstnRegistFormData {
|
||||
const detDto = data.detDto;
|
||||
@@ -33,15 +37,8 @@ export function mapDetailToFormData(data: DesignCnstnMngDetResponseDto): DesignC
|
||||
envoSeComCd: detDto.envoSeComCd,
|
||||
prgrsSttsComCd: detDto.prgrsSttsComCd,
|
||||
atchFileGroupId: detDto.atchFileGroupId ?? "",
|
||||
envoSeCdNm: detDto.envoSeCdNm,
|
||||
taskSeCdNm: detDto.taskSeCdNm,
|
||||
prgrsSttsCdNm: detDto.prgrsSttsCdNm,
|
||||
sido: detDto.sido,
|
||||
gugun: detDto.gugun,
|
||||
regDt: detDto.regDt,
|
||||
rgtr: detDto.rgtr,
|
||||
rgtrNm: detDto.rgtrNm,
|
||||
mdfcnDt: detDto.mdfcnDt,
|
||||
},
|
||||
bzScaleList: bizSclList.map((item) => ({
|
||||
cnstpSeCd: item.cnstpSeCd,
|
||||
@@ -90,3 +87,20 @@ export function getCreateDefaultValues(
|
||||
privacyConsent: "N",
|
||||
};
|
||||
}
|
||||
|
||||
export function buildFormPayload(
|
||||
data: DesignCnstnRegistFormData,
|
||||
prgrsSttsComCd: "CV" | "CT",
|
||||
options: { isEditMode: boolean; docNo?: string },
|
||||
): DesignCnstnApplySaveRequestDto {
|
||||
return designCnstnApplySaveSchema.parse({
|
||||
designCnstnMng: {
|
||||
...data.designCnstnMng,
|
||||
taskSeComCd: formatTaskSeComCdForApi(data.designCnstnMng.taskSeComCd),
|
||||
designCnstnDocNo: options.isEditMode ? options.docNo : undefined,
|
||||
prgrsSttsComCd,
|
||||
},
|
||||
bzScaleList: data.bzScaleList,
|
||||
esntlAtchFileList: data.esntlAtchFileList,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import type {
|
||||
DesignCnstnApplySaveRequestDto,
|
||||
DesignCnstnMngDetResponseDto,
|
||||
} from "@/entities/design-cnstn";
|
||||
import { designCnstnApplySaveSchema } from "@/entities/design-cnstn";
|
||||
import { toEsntlAtchFileSaveList } from "@/widgets/pbadms-sprt";
|
||||
|
||||
import type { ReviewStepCode } from "../model/view-types";
|
||||
@@ -30,11 +31,11 @@ export function buildSavePayload(
|
||||
throw new Error("상세 정보(detDto)가 없습니다.");
|
||||
}
|
||||
|
||||
return {
|
||||
return designCnstnApplySaveSchema.parse({
|
||||
designCnstnMng: { ...detDto, prgrsSttsComCd },
|
||||
bzScaleList: toBzScaleSaveDto(data.bizSclList ?? []),
|
||||
esntlAtchFileList: toEsntlAtchFileSaveList(data.esntlFileList),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function buildTechReviewPayload(
|
||||
@@ -45,10 +46,10 @@ export function buildTechReviewPayload(
|
||||
reviewOpinionFormData?: ReviewOpinionGetFormDataResult | null,
|
||||
opnrNm?: string,
|
||||
): DesignCnstnApplySaveRequestDto {
|
||||
return {
|
||||
return designCnstnApplySaveSchema.parse({
|
||||
...buildSavePayload(data, prgrsSttsComCd),
|
||||
techReViewSaveCd,
|
||||
rvwStpCd: reviewStep,
|
||||
rvwOpnnList: toRvwOpnnSaveDto(data, reviewOpinionFormData, opnrNm),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,9 +9,8 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { SectionCollapsibleGroup, SectionCollapsibleToggle } from "@/shared/ui";
|
||||
import { RhfDevTool, SectionCollapsibleGroup, SectionCollapsibleToggle } from "@/shared/ui";
|
||||
import {
|
||||
type DesignCnstnApplySaveRequestDto,
|
||||
designCnstnKeys,
|
||||
type DesignCnstnMngDetResponseDto,
|
||||
designCnstnMngService,
|
||||
@@ -27,12 +26,12 @@ import {
|
||||
} from "@/widgets/pbadms-sprt";
|
||||
import { Button } from "@common/components";
|
||||
|
||||
import { type DesignCnstnRegistFormData, designCnstnRegistSchema } from "../lib/design-cnstn-regist-schema";
|
||||
import {
|
||||
type DesignCnstnRegistFormData,
|
||||
designCnstnRegistSchema,
|
||||
formatTaskSeComCdForApi,
|
||||
} from "../lib/design-cnstn-regist-schema";
|
||||
import { getCreateDefaultValues, mapDetailToFormData } from "../lib/form-data-mapper";
|
||||
buildFormPayload,
|
||||
getCreateDefaultValues,
|
||||
mapDetailToFormData,
|
||||
} from "../lib/form-data-mapper";
|
||||
import { PROGRESS_STATUS_COLUMNS } from "../model/columns";
|
||||
|
||||
import { BasicInfoFormSection } from "./sections/basic-info-form-section";
|
||||
@@ -68,29 +67,18 @@ function DesignFormPage({ mode, initialData, createDefaults }: DesignFormPagePro
|
||||
: getCreateDefaultValues(userInfo, createDefaults),
|
||||
});
|
||||
|
||||
function buildFormPayload(
|
||||
data: DesignCnstnRegistFormData,
|
||||
prgrsSttsComCd: "CV" | "CT",
|
||||
): DesignCnstnApplySaveRequestDto {
|
||||
const { taskSeComCd: formTaskSeComCd, ...designCnstnMngRest } = data.designCnstnMng;
|
||||
|
||||
return {
|
||||
designCnstnMng: {
|
||||
...designCnstnMngRest,
|
||||
taskSeComCd: formatTaskSeComCdForApi(formTaskSeComCd),
|
||||
designCnstnDocNo: isEditMode ? initialData?.detDto?.designCnstnDocNo : undefined,
|
||||
prgrsSttsComCd,
|
||||
},
|
||||
bzScaleList: data.bzScaleList,
|
||||
esntlAtchFileList: data.esntlAtchFileList,
|
||||
};
|
||||
function createFormPayload(data: DesignCnstnRegistFormData, prgrsSttsComCd: "CV" | "CT") {
|
||||
return buildFormPayload(data, prgrsSttsComCd, {
|
||||
isEditMode,
|
||||
docNo: initialData?.detDto?.designCnstnDocNo,
|
||||
});
|
||||
}
|
||||
|
||||
async function submitForm(
|
||||
data: DesignCnstnRegistFormData,
|
||||
prgrsSttsComCd: "CV" | "CT",
|
||||
): Promise<string | undefined> {
|
||||
return designCnstnMngService.saveApply(buildFormPayload(data, prgrsSttsComCd));
|
||||
return designCnstnMngService.saveApply(createFormPayload(data, prgrsSttsComCd));
|
||||
}
|
||||
|
||||
async function onSave(data: DesignCnstnRegistFormData) {
|
||||
@@ -229,6 +217,7 @@ function DesignFormPage({ mode, initialData, createDefaults }: DesignFormPagePro
|
||||
</div>
|
||||
</SectionCollapsibleGroup>
|
||||
</form>
|
||||
<RhfDevTool control={form.control} />
|
||||
</FormProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,14 +2,11 @@
|
||||
|
||||
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
|
||||
|
||||
import { Paperclip } from "lucide-react";
|
||||
|
||||
import { PROXY_HOST } from "@/shared/config";
|
||||
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 { attachService } from "@/entities/attach";
|
||||
import { AttachmentDownloadLink, attachService } from "@/entities/attach";
|
||||
import type { DesignCnstnRvwOpnnResponseDto } from "@/entities/design-cnstn";
|
||||
import {
|
||||
Button,
|
||||
@@ -319,15 +316,10 @@ function TechReviewOpinionTable({ items }: TechReviewOpinionTableProps) {
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
{item.atchFileInfo?.atflCd ? (
|
||||
<a
|
||||
className="inline-flex cursor-pointer items-start gap-1 text-left text-blue-600 hover:underline"
|
||||
href={`${PROXY_HOST}/attach-service/api/v1/atflcd/download/${item.atchFileInfo.atflCd}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<Paperclip className="my-1 size-5 shrink-0" />
|
||||
{item.atchFileInfo.orgnlFileNm || "다운로드"}
|
||||
</a>
|
||||
<AttachmentDownloadLink
|
||||
atflCd={item.atchFileInfo.atflCd}
|
||||
fileName={item.atchFileInfo.orgnlFileNm || "다운로드"}
|
||||
/>
|
||||
) : (
|
||||
"-"
|
||||
)}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useForm } from "react-hook-form";
|
||||
|
||||
import { getDefaultFileKey, useTus } from "@/shared/lib";
|
||||
import { AttachmentFileField } from "@/shared/ui";
|
||||
import { attachService } from "@/entities/attach";
|
||||
import { attachService, useDownloadAttachmentByAtflId } from "@/entities/attach";
|
||||
import { CodeSelect } from "@/entities/code";
|
||||
import type { EsntlAtchFileMng } from "@/entities/esntl";
|
||||
import {
|
||||
@@ -61,6 +61,7 @@ function EsntlAtchFileFormDialog({
|
||||
const formId = useId();
|
||||
const atchFileGroupIdRef = useRef("");
|
||||
const [uploadedFiles, setUploadedFiles] = useState<UploadedTemplateFile[]>([]);
|
||||
const { mutate: downloadByAtflId } = useDownloadAttachmentByAtflId();
|
||||
|
||||
const { register, handleSubmit, reset, setValue, watch } = useForm<EsntlAtchFileFormValues>({
|
||||
defaultValues: {
|
||||
@@ -219,6 +220,9 @@ function EsntlAtchFileFormDialog({
|
||||
files={uploadedFiles.map((item) => ({
|
||||
fileKey: item.fileKey,
|
||||
fileName: item.fileName,
|
||||
onDownload: item.atflId
|
||||
? () => downloadByAtflId(item.atflId!)
|
||||
: undefined,
|
||||
}))}
|
||||
inputId={fileInputId}
|
||||
isUploadingByKey={isUploading}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { RagIndexingInfoDto } from "@/entities/fnrs-cnsltn";
|
||||
|
||||
const AI_PENDING_STATUS_CODES = ["FV", "AC", "AV"] as const;
|
||||
|
||||
type AiPendingStatusCode = (typeof AI_PENDING_STATUS_CODES)[number];
|
||||
@@ -17,5 +19,17 @@ function getAiPendingMessage(prgrsSttsComCd: string): string {
|
||||
return AI_PENDING_MESSAGES[prgrsSttsComCd as AiPendingStatusCode];
|
||||
}
|
||||
|
||||
export { AI_PENDING_STATUS_CODES, getAiPendingMessage, isAiPendingStatus };
|
||||
function isRagIndexingInProgress(
|
||||
prgrsSttsComCd: string,
|
||||
ragIndexingInfo?: RagIndexingInfoDto | null,
|
||||
): boolean {
|
||||
return prgrsSttsComCd === "ER" && ragIndexingInfo?.isInProgress === true;
|
||||
}
|
||||
|
||||
export {
|
||||
AI_PENDING_STATUS_CODES,
|
||||
getAiPendingMessage,
|
||||
isAiPendingStatus,
|
||||
isRagIndexingInProgress,
|
||||
};
|
||||
export type { AiPendingStatusCode };
|
||||
|
||||
@@ -2,14 +2,14 @@ export const FNRS_CNSLTN_VIEW_BUTTON_ACTION = {
|
||||
strg: "strg",
|
||||
aply: "aply",
|
||||
del: "del",
|
||||
fnrsCnsltnAncmnt: "fnrsCnsltnAncmnt",
|
||||
envoSbmsn: "envoSbmsn",
|
||||
rjct: "rjct",
|
||||
sncnAprv: "sncnAprv",
|
||||
lclRply: "lclRply",
|
||||
rvwRply: "rvwRply",
|
||||
rvwOpnnRply: "rvwOpnnRply",
|
||||
rqstTechRvw: "rqstTechRvw",
|
||||
fileSplmnt: "fileSplmnt",
|
||||
fileVrfc: "fileVrfc",
|
||||
dsgntRvwr: "dsgntRvwr",
|
||||
pwrkCnsltnDocCrt: "pwrkCnsltnDocCrt",
|
||||
pwrkCnsltnDocMng: "pwrkCnsltnDocMng",
|
||||
@@ -21,6 +21,7 @@ export const FNRS_CNSLTN_VIEW_BUTTON_ACTION = {
|
||||
atrzRply: "atrzRply",
|
||||
actnCmptn: "actnCmptn",
|
||||
rqstActnRvw: "rqstActnRvw",
|
||||
costVrfc: "costVrfc",
|
||||
} as const;
|
||||
|
||||
export type FnrsCnsltnViewButtonAction =
|
||||
@@ -38,37 +39,63 @@ const ROLE_STATUS_BUTTON_ACTIONS: Record<string, Record<string, FnrsCnsltnViewBu
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.aply,
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.del,
|
||||
],
|
||||
RB: [
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng,
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.actnCmptn,
|
||||
],
|
||||
TA: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.fnrsCnsltnAncmnt],
|
||||
RB: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng, FNRS_CNSLTN_VIEW_BUTTON_ACTION.actnCmptn],
|
||||
RA: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
TA: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
},
|
||||
ROLE_USER04: {
|
||||
ST: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.envoSbmsn, FNRS_CNSLTN_VIEW_BUTTON_ACTION.rjct],
|
||||
RM: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.lclRply],
|
||||
EP: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.sncnAprv],
|
||||
EP: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.sncnAprv, FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
},
|
||||
ROLE_USER03: {
|
||||
LE: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwRply, FNRS_CNSLTN_VIEW_BUTTON_ACTION.rqstTechRvw],
|
||||
TP: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwRply],
|
||||
TP: [
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng,
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwRply,
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwOpnnRply,
|
||||
],
|
||||
RA: [
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng,
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.rqstActnRvw,
|
||||
],
|
||||
RR: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng, FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwRply],
|
||||
},
|
||||
};
|
||||
|
||||
const ROLE_USER01_ATRZ_STATUS_CODES = ["ER", "TE", "RK"] as const;
|
||||
const ROLE_USER01_ATRZ_STATUS_CODES = [
|
||||
"ER",
|
||||
"SL",
|
||||
"TE",
|
||||
"TP",
|
||||
"RB",
|
||||
"RA",
|
||||
"AV",
|
||||
"EV",
|
||||
"RK",
|
||||
"RR",
|
||||
"EP",
|
||||
"TA",
|
||||
] as const;
|
||||
|
||||
const ROLE_USER01_ATRZ_STATUS_ACTIONS: Record<
|
||||
string,
|
||||
Record<string, FnrsCnsltnViewButtonAction[]>
|
||||
> = {
|
||||
ER: {
|
||||
ATRZ01: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.fileSplmnt, FNRS_CNSLTN_VIEW_BUTTON_ACTION.dsgntRvwr],
|
||||
ATRZ01: [
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.fileSplmnt,
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.fileVrfc,
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.dsgntRvwr,
|
||||
],
|
||||
},
|
||||
SL: {
|
||||
ATRZ01: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
ATRZ02: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
ATRZ03: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
ATRZ99: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
},
|
||||
TE: {
|
||||
ATRZ01: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
ATRZ02: [
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocCrt,
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng,
|
||||
@@ -81,10 +108,80 @@ const ROLE_USER01_ATRZ_STATUS_ACTIONS: Record<
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng,
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.atrzAprv,
|
||||
],
|
||||
ATRZ99: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.atrzRply],
|
||||
ATRZ99: [
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng,
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.atrzRply,
|
||||
],
|
||||
},
|
||||
TP: {
|
||||
ATRZ01: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
ATRZ02: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
ATRZ03: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
ATRZ99: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
},
|
||||
RB: {
|
||||
ATRZ01: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
ATRZ02: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
ATRZ03: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
ATRZ99: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
},
|
||||
RA: {
|
||||
ATRZ01: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
ATRZ02: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
ATRZ03: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
ATRZ99: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
},
|
||||
AV: {
|
||||
ATRZ01: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
ATRZ02: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
ATRZ03: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
ATRZ99: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
},
|
||||
EV: {
|
||||
ATRZ01: [
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng,
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.dsgntRvwr,
|
||||
],
|
||||
ATRZ02: [
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng,
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.costVrfc,
|
||||
],
|
||||
ATRZ03: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
ATRZ99: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
},
|
||||
RK: {
|
||||
ATRZ01: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
ATRZ02: [
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng,
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwCmptn,
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.dmndSplmnt,
|
||||
],
|
||||
ATRZ03: [
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng,
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.atrzAprv,
|
||||
],
|
||||
ATRZ99: [
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng,
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.atrzRply,
|
||||
],
|
||||
},
|
||||
RR: {
|
||||
ATRZ01: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
ATRZ02: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
ATRZ03: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
ATRZ99: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
},
|
||||
EP: {
|
||||
ATRZ01: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
ATRZ02: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
ATRZ03: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
ATRZ99: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
},
|
||||
TA: {
|
||||
ATRZ01: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
ATRZ02: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
ATRZ03: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
ATRZ99: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -95,10 +192,16 @@ function resolveButtonConfigs(actions: FnrsCnsltnViewButtonAction[]): FnrsCnsltn
|
||||
interface GetButtonConfigsOptions {
|
||||
hasPendingApprovalRequest?: boolean;
|
||||
hasCompletedDraft?: boolean;
|
||||
canRetryFileVrfc?: boolean;
|
||||
canRetryDraftGen?: boolean;
|
||||
canRetryCostVrfc?: boolean;
|
||||
}
|
||||
|
||||
const PENDING_APPROVAL_ACTIONS = new Set<FnrsCnsltnViewButtonAction>([
|
||||
...ROLE_USER01_ATRZ_STATUS_ACTIONS.TE.ATRZ02,
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwRsltInq,
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwStrg,
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.dmndSplmnt,
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwCmptn,
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.atrzAprv,
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.atrzRply,
|
||||
]);
|
||||
@@ -129,15 +232,26 @@ function resolveRoleUser01AtrzButtonActions(
|
||||
const needsPendingApproval = PENDING_APPROVAL_ACTIONS.has(action);
|
||||
if (needsPendingApproval && options?.hasPendingApprovalRequest === false) return false;
|
||||
|
||||
if (prgrsSttsComCd === "TE" && options?.hasCompletedDraft != null) {
|
||||
if (
|
||||
action === FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng ||
|
||||
action === FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwRsltInq
|
||||
) {
|
||||
return options.hasCompletedDraft;
|
||||
if (action === FNRS_CNSLTN_VIEW_BUTTON_ACTION.fileVrfc) {
|
||||
return options?.canRetryFileVrfc === true;
|
||||
}
|
||||
if (action === FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocCrt) {
|
||||
return options?.canRetryDraftGen === true;
|
||||
}
|
||||
if (action === FNRS_CNSLTN_VIEW_BUTTON_ACTION.costVrfc) {
|
||||
return options?.canRetryCostVrfc === true;
|
||||
}
|
||||
|
||||
if (options?.hasCompletedDraft != null) {
|
||||
if (prgrsSttsComCd === "SL" || prgrsSttsComCd === "TE" || prgrsSttsComCd === "RK") {
|
||||
if (action === FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng) {
|
||||
return options.hasCompletedDraft;
|
||||
}
|
||||
}
|
||||
if (action === FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocCrt) {
|
||||
return !options.hasCompletedDraft;
|
||||
if (prgrsSttsComCd === "TE") {
|
||||
if (action === FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwRsltInq) {
|
||||
return options.hasCompletedDraft;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
fnrsCnsltnBizCstItemSchema,
|
||||
fnrsCnsltnBzScaleItemSchema,
|
||||
fnrsCnsltnCklstResultItemSchema,
|
||||
fnrsCnsltnEsntlAtchFileItemSchema,
|
||||
fnrsCnsltnMngSaveSchema,
|
||||
fnrsCnsltnSplmntDmndItemSchema,
|
||||
fnrsSharItemSchema,
|
||||
} from "@/entities/fnrs-cnsltn";
|
||||
|
||||
export const fnrsCnsltnRegistSchema = z.object({
|
||||
fnrsCnsltnMng: z.object({
|
||||
fnrsCnsltnMng: fnrsCnsltnMngSaveSchema.extend({
|
||||
bizNm: z.string().min(1, "사업명은 필수입니다"),
|
||||
bizBgngYr: z.string().min(1, "사업시작연도는 필수입니다"),
|
||||
bizEndYr: z.string().min(1, "사업종료연도는 필수입니다"),
|
||||
bizCn: z.string().min(1, "사업내용은 필수입니다"),
|
||||
taskSeComCd: z.array(z.string()).min(1, "구분은 필수입니다"),
|
||||
cfmtnBgtBizNo: z.string().optional(),
|
||||
bgtAplyYr: z.string().optional(),
|
||||
dsgnrNm: z.string().min(1, "설계자 이름은 필수입니다"),
|
||||
dsgnrTelno: z
|
||||
.string()
|
||||
@@ -19,30 +27,11 @@ export const fnrsCnsltnRegistSchema = z.object({
|
||||
.string()
|
||||
.min(1, "설계자 이메일은 필수입니다")
|
||||
.email("올바른 이메일 형식이 아닙니다"),
|
||||
aplcntNm: z.string().optional(),
|
||||
aplcntMblTelno: z.string().optional(),
|
||||
aplcntOgdpNm: z.string().optional(),
|
||||
aplcntEmlAddr: z.string().optional(),
|
||||
fnrsCnsltnDocNo: z.string().optional(),
|
||||
admdstCd: z.string().optional(),
|
||||
envoSeComCd: z.string().optional(),
|
||||
rtlztnFcltSeComCd: z.string().optional(),
|
||||
rtlztnUsgSeComCd: z.string().optional(),
|
||||
prgrsSttsComCd: z.string().optional(),
|
||||
atchFileGroupId: z.string().optional(),
|
||||
sharAtchFileGroupId: z.string().optional(),
|
||||
envoSeCdNm: z.string().optional(),
|
||||
taskSeCdNm: z.string().optional(),
|
||||
prgrsSttsCdNm: z.string().optional(),
|
||||
sido: z.string().optional(),
|
||||
gugun: z.string().optional(),
|
||||
regDt: z.string().optional(),
|
||||
rgtr: z.string().optional(),
|
||||
rgtrNm: z.string().optional(),
|
||||
mdfcnDt: z.string().optional(),
|
||||
}),
|
||||
bzScaleList: z.array(
|
||||
z.object({
|
||||
fnrsCnsltnBzScaleItemSchema.extend({
|
||||
cnstpSeCd: z.string(),
|
||||
bscPl: z.number(),
|
||||
budgetReflct: z.number(),
|
||||
@@ -51,7 +40,7 @@ export const fnrsCnsltnRegistSchema = z.object({
|
||||
}),
|
||||
),
|
||||
bizCstList: z.array(
|
||||
z.object({
|
||||
fnrsCnsltnBizCstItemSchema.extend({
|
||||
acntgSeCd: z.string(),
|
||||
bscPlanAmt: z.number(),
|
||||
bgtRfltAmt: z.number(),
|
||||
@@ -59,7 +48,7 @@ export const fnrsCnsltnRegistSchema = z.object({
|
||||
}),
|
||||
),
|
||||
fnrsSharList: z.array(
|
||||
z.object({
|
||||
fnrsSharItemSchema.extend({
|
||||
fnrsSharSeCd: z.string(),
|
||||
exstnAmt: z.number(),
|
||||
chgRqtAmt: z.number(),
|
||||
@@ -68,37 +57,24 @@ export const fnrsCnsltnRegistSchema = z.object({
|
||||
}),
|
||||
),
|
||||
cklstResultList: z.array(
|
||||
z.object({
|
||||
fnrsCnsltnCklstResultItemSchema.extend({
|
||||
cklstSn: z.number(),
|
||||
cklstArtclSn: z.number(),
|
||||
cklstRvwSn: z.number(),
|
||||
rvwStpComCd: z.string(),
|
||||
selected: z.enum(["Y", "N"]).nullable(),
|
||||
picRvwCn: z.string().nullable().optional(),
|
||||
selected: z.enum(["Y", "N"]).nullish(),
|
||||
picRvwCn: z.string().nullish(),
|
||||
}),
|
||||
),
|
||||
esntlAtchFileList: z.array(
|
||||
z.object({
|
||||
fnrsCnsltnEsntlAtchFileItemSchema.extend({
|
||||
esntlAtchFileId: z.string(),
|
||||
atchFileGroupId: z.string(),
|
||||
}),
|
||||
),
|
||||
splmntDmndList: z.array(
|
||||
z.object({
|
||||
splmntDmndSn: z.number().nullish(),
|
||||
splmntArtclComCd: z.string().nullish(),
|
||||
fnrsCnsltnSplmntDmndItemSchema.extend({
|
||||
splmntArtclCdNm: z.string().nullish(),
|
||||
splmntDmndCn: z.string().optional(),
|
||||
splmntDmndDt: z.string().optional(),
|
||||
splmntDmndAtchFileGroupId: z.string().optional(),
|
||||
splmntDmndAtchFileNm: z.string().optional(),
|
||||
splmntCn: z.string().optional(),
|
||||
splmntPicNm: z.string().optional(),
|
||||
splmntCmptnDt: z.string().optional(),
|
||||
splmntAtchFileGroupId: z.string().optional(),
|
||||
splmntAtchFileNm: z.string().optional(),
|
||||
splmntAtchFileAtflId: z.string().optional(),
|
||||
cmptnYn: z.string().optional(),
|
||||
}),
|
||||
),
|
||||
privacyConsent: z.enum(["Y", "N"], {
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
import type { IUserInfo } from "@/shared/store";
|
||||
import type { FnrsCnsltnMngDetResponseDto } from "@/entities/fnrs-cnsltn";
|
||||
import { mapFnrsSharDetToFormList, toEsntlAtchFileSaveList } from "@/widgets/pbadms-sprt";
|
||||
import {
|
||||
type FnrsCnsltnApplySaveRequestDto,
|
||||
fnrsCnsltnApplySaveSchema,
|
||||
type FnrsCnsltnMngDetResponseDto,
|
||||
} from "@/entities/fnrs-cnsltn";
|
||||
import {
|
||||
type CostItemDef,
|
||||
type FnrsSharItemDef,
|
||||
mapFnrsSharDetToFormList,
|
||||
recalculateFnrsSharTotals,
|
||||
toEsntlAtchFileSaveList,
|
||||
toFnrsSharSaveList,
|
||||
validateBizCstSharBalance,
|
||||
} from "@/widgets/pbadms-sprt";
|
||||
|
||||
import type { FnrsCnsltnRegistFormData } from "./fnrs-cnsltn-regist-schema";
|
||||
import { parseTaskSeComCdFromApi } from "./fnrs-cnsltn-regist-schema";
|
||||
import { formatTaskSeComCdForApi, parseTaskSeComCdFromApi } from "./fnrs-cnsltn-regist-schema";
|
||||
|
||||
function mapSelectedValue(value?: string | null): "Y" | "N" | null {
|
||||
if (value === "Y" || value === "N") return value;
|
||||
@@ -47,15 +59,8 @@ export function mapDetailToFormData(data: FnrsCnsltnMngDetResponseDto): FnrsCnsl
|
||||
prgrsSttsComCd: detDto.prgrsSttsComCd,
|
||||
atchFileGroupId: detDto.atchFileGroupId ?? "",
|
||||
sharAtchFileGroupId: detDto.sharAtchFileGroupId ?? "",
|
||||
envoSeCdNm: detDto.envoSeCdNm,
|
||||
taskSeCdNm: detDto.taskSeCdNm,
|
||||
prgrsSttsCdNm: detDto.prgrsSttsCdNm,
|
||||
sido: detDto.sido,
|
||||
gugun: detDto.gugun,
|
||||
regDt: detDto.regDt,
|
||||
rgtr: detDto.rgtr,
|
||||
rgtrNm: detDto.rgtrNm,
|
||||
mdfcnDt: detDto.mdfcnDt,
|
||||
},
|
||||
bzScaleList: bizSclList.map((item) => ({
|
||||
cnstpSeCd: item.cnstpSeCd,
|
||||
@@ -94,8 +99,6 @@ export function mapDetailToFormData(data: FnrsCnsltnMngDetResponseDto): FnrsCnsl
|
||||
splmntPicNm: item.splmntPicNm ?? "",
|
||||
splmntCmptnDt: item.splmntCmptnDt ?? "",
|
||||
splmntAtchFileGroupId: item.splmntAtchFileGroupId ?? "",
|
||||
splmntAtchFileNm: item.splmntAtchFileInfo?.orgnlFileNm ?? "",
|
||||
splmntAtchFileAtflId: item.splmntAtchFileInfo?.atflId ?? "",
|
||||
cmptnYn: item.cmptnYn,
|
||||
})) ?? [],
|
||||
privacyConsent: "Y",
|
||||
@@ -146,3 +149,46 @@ export function getCreateDefaultValues(
|
||||
privacyConsent: "N",
|
||||
};
|
||||
}
|
||||
|
||||
export function buildFormPayload(
|
||||
data: FnrsCnsltnRegistFormData,
|
||||
prgrsSttsComCd: string,
|
||||
options: {
|
||||
isEditMode: boolean;
|
||||
docNo?: string;
|
||||
sharItems: FnrsSharItemDef[];
|
||||
costItems: CostItemDef[];
|
||||
},
|
||||
): FnrsCnsltnApplySaveRequestDto | { validationMessage: string } {
|
||||
const withTotals = recalculateFnrsSharTotals(data.fnrsSharList, options.sharItems);
|
||||
const validationMessage = validateBizCstSharBalance(
|
||||
data.bizCstList,
|
||||
withTotals,
|
||||
options.sharItems,
|
||||
options.costItems,
|
||||
);
|
||||
if (validationMessage) {
|
||||
return { validationMessage };
|
||||
}
|
||||
|
||||
return fnrsCnsltnApplySaveSchema.parse({
|
||||
fnrsCnsltnMng: {
|
||||
...data.fnrsCnsltnMng,
|
||||
taskSeComCd: formatTaskSeComCdForApi(data.fnrsCnsltnMng.taskSeComCd),
|
||||
fnrsCnsltnDocNo: options.isEditMode ? options.docNo : undefined,
|
||||
prgrsSttsComCd,
|
||||
},
|
||||
bzScaleList: data.bzScaleList,
|
||||
bizCstList: data.bizCstList,
|
||||
fnrsSharList: toFnrsSharSaveList(withTotals),
|
||||
cklstResultList: data.cklstResultList.map((item) => ({
|
||||
...item,
|
||||
selected: item.selected ?? undefined,
|
||||
})),
|
||||
esntlAtchFileList: data.esntlAtchFileList,
|
||||
splmntDmndList: data.splmntDmndList.map((item) => ({
|
||||
...item,
|
||||
...(prgrsSttsComCd !== "SV" && prgrsSttsComCd !== "ST" && { cmptnYn: "Y" }),
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -32,7 +32,10 @@ export function getEditableReviewStep(
|
||||
dtlInstCd?: string,
|
||||
prgrsSttsComCd?: string,
|
||||
): ReviewStepCode | undefined {
|
||||
if (dtlInstCd === "ROLE_USER03" && (prgrsSttsComCd === "LE" || prgrsSttsComCd === "TP")) {
|
||||
if (
|
||||
dtlInstCd === "ROLE_USER03" &&
|
||||
(prgrsSttsComCd === "LE" || prgrsSttsComCd === "TP" || prgrsSttsComCd === "RR")
|
||||
) {
|
||||
return "ENV";
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import type {
|
||||
FnrsCnsltnMngResponseDto,
|
||||
FnrsCnsltnSplmntDmndItem,
|
||||
} from "@/entities/fnrs-cnsltn";
|
||||
import { fnrsCnsltnApplySaveSchema } from "@/entities/fnrs-cnsltn";
|
||||
import type { PwrkCnsltnCnsltnResponseDto } from "@/entities/pwrk-cnsltn-mng";
|
||||
import {
|
||||
mapFnrsSharDetToFormList,
|
||||
@@ -61,14 +62,14 @@ export function buildSavePayload(
|
||||
const cklstResultList = data.cklstResultList ?? [];
|
||||
const esntlFileList = data.esntlFileList ?? [];
|
||||
|
||||
return {
|
||||
return fnrsCnsltnApplySaveSchema.parse({
|
||||
fnrsCnsltnMng: { ...detDto, prgrsSttsComCd },
|
||||
bzScaleList: toBzScaleSaveDto(bizSclList),
|
||||
bizCstList: toBizCstSaveDto(bizCstList),
|
||||
cklstResultList: toCklstSaveDto(cklstResultList, checklistFormState, reviewStep),
|
||||
esntlAtchFileList: toEsntlAtchFileSaveList(esntlFileList),
|
||||
fnrsSharList: toFnrsSharSaveList(mapFnrsSharDetToFormList(data.fnrsSharList ?? [])),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function buildTechReviewReplyPayload(
|
||||
@@ -80,12 +81,12 @@ export function buildTechReviewReplyPayload(
|
||||
reviewOpinionFormData?: ReviewOpinionGetFormDataResult | null,
|
||||
opnrNm?: string,
|
||||
): FnrsCnsltnApplySaveRequestDto {
|
||||
return {
|
||||
return fnrsCnsltnApplySaveSchema.parse({
|
||||
...buildSavePayload(data, checklistFormState, reviewStep, prgrsSttsComCd),
|
||||
techReViewSaveCd,
|
||||
rvwStpCd: reviewStep,
|
||||
rvwOpnnList: toRvwOpnnSaveDto(data, reviewOpinionFormData, opnrNm),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function buildSupplementReqPayload(
|
||||
@@ -107,10 +108,10 @@ export function buildSupplementReqPayload(
|
||||
cmptnYn: "N",
|
||||
}));
|
||||
|
||||
return {
|
||||
return fnrsCnsltnApplySaveSchema.parse({
|
||||
...buildSavePayload(data, checklistFormState, "LOCAL", "SL"),
|
||||
splmntDmndList: [...existingItems, ...newItems],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function buildFileSplmntReqPayload(
|
||||
@@ -124,10 +125,10 @@ export function buildFileSplmntReqPayload(
|
||||
cmptnYn: "N",
|
||||
}));
|
||||
|
||||
return {
|
||||
return fnrsCnsltnApplySaveSchema.parse({
|
||||
...buildSavePayload(data, checklistFormState, "LOCAL", "SL"),
|
||||
splmntDmndList: [...newItems],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function buildRvwRsltSplmntReqPayload(
|
||||
@@ -144,10 +145,10 @@ export function buildRvwRsltSplmntReqPayload(
|
||||
cmptnYn: "N",
|
||||
}));
|
||||
|
||||
return {
|
||||
return fnrsCnsltnApplySaveSchema.parse({
|
||||
...buildSavePayload(data, checklistFormState, "LOCAL", "SL"),
|
||||
splmntDmndList: [...existingItems, ...newItems],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function toSplmntDmndSaveItem(item: FnrsCnsltnSplmntDmndItem): FnrsCnsltnSplmntDmndItem {
|
||||
@@ -169,21 +170,21 @@ export function buildSupplementSubmitPayload(
|
||||
data: FnrsCnsltnMngDetResponseDto,
|
||||
checklistFormState: ChecklistFormState,
|
||||
): FnrsCnsltnApplySaveRequestDto {
|
||||
return {
|
||||
return fnrsCnsltnApplySaveSchema.parse({
|
||||
...buildSavePayload(data, checklistFormState, "LOCAL", "SL"),
|
||||
splmntDmndList: data.splmntDmndList,
|
||||
};
|
||||
splmntDmndList: (data.splmntDmndList ?? []).map(toSplmntDmndSaveItem),
|
||||
});
|
||||
}
|
||||
|
||||
export function buildTechReviewPayload(
|
||||
det: FnrsCnsltnMngResponseDto,
|
||||
cklstResultList: CklstResultItem[],
|
||||
): FnrsCnsltnApplySaveRequestDto {
|
||||
return {
|
||||
return fnrsCnsltnApplySaveSchema.parse({
|
||||
fnrsCnsltnMng: { ...det, prgrsSttsComCd: "ER" },
|
||||
bzScaleList: [],
|
||||
bizCstList: [],
|
||||
cklstResultList,
|
||||
esntlAtchFileList: [],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ const columnHelper = createColumnHelper<FnrsCnsltnMngResponseDto>();
|
||||
|
||||
const MAIN_STEP_LABELS = {
|
||||
apply: "재원협의 신청",
|
||||
envoReview: "환경청 검토",
|
||||
envoReview: "재원협의 검토",
|
||||
techReview: "기술검토",
|
||||
reply: "검토결과 회신",
|
||||
} as const;
|
||||
@@ -20,7 +20,7 @@ const MAIN_STEP_LABELS = {
|
||||
export const PROGRESS_STATUS_COLUMNS = [
|
||||
{ label: "재원협의 신청 (시군)", sttsComCd: "ST" },
|
||||
{ label: "재원협의 신청 (시도)", sttsComCd: "LE" },
|
||||
{ label: "환경청 검토", sttsComCd: "ER" },
|
||||
{ label: "재원협의 검토", sttsComCd: "ER" },
|
||||
{ label: "기술검토", sttsComCd: "TE" },
|
||||
{ label: "기술검토 회신", sttsComCd: "TP" },
|
||||
{ label: "환경청 검토 결과 회신", sttsComCd: "EP" },
|
||||
@@ -61,7 +61,7 @@ function buildProgressBadges(prgrsSttsComCd: string, techRplyHstryYn: "Y" | "N")
|
||||
];
|
||||
}
|
||||
|
||||
if (prgrsSttsComCd === "ER" || prgrsSttsComCd === "TE" || prgrsSttsComCd === "SL") {
|
||||
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" },
|
||||
@@ -82,7 +82,7 @@ function buildProgressBadges(prgrsSttsComCd: string, techRplyHstryYn: "Y" | "N")
|
||||
];
|
||||
}
|
||||
|
||||
if (prgrsSttsComCd === "EP") {
|
||||
if (prgrsSttsComCd === "EP" || prgrsSttsComCd === "TA") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.envoReview, state: "completed" },
|
||||
|
||||
@@ -61,16 +61,25 @@ export const TASK_CODE_LABELS: Record<string, string> = {
|
||||
};
|
||||
|
||||
export const PBADMS_PROGRESS_ORDER = [
|
||||
// LOCAL
|
||||
"SV",
|
||||
"SL",
|
||||
"TA",
|
||||
"ST",
|
||||
"SL",
|
||||
"RB",
|
||||
"EP",
|
||||
"TA",
|
||||
|
||||
// ENV
|
||||
"LE",
|
||||
"TP",
|
||||
"RA",
|
||||
"RR",
|
||||
|
||||
// KECO
|
||||
"ER",
|
||||
"TE",
|
||||
"TB",
|
||||
"EV",
|
||||
"RK",
|
||||
] as const;
|
||||
|
||||
export const ROLE_TO_REVIEW_STEP: Record<string, ReviewStepCode> = {
|
||||
|
||||
@@ -9,11 +9,19 @@ import { AxiosError } from "axios";
|
||||
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import {
|
||||
AI_AUTO_SE,
|
||||
type AiAutoPrcsLatestResponseDto,
|
||||
useAiAutoPrcsLatest,
|
||||
} from "@/entities/ai-auto-prcs-mng";
|
||||
import { useReGenerateDraft, useReTransferDocument, useReVerifyCost } from "@/entities/dcrag-to";
|
||||
import {
|
||||
buildElaprPrgrsSttsListFromAplnRows,
|
||||
ElaprApprovalDialog,
|
||||
type ElaprDmndSaveRequestDto,
|
||||
elaprDmndService,
|
||||
type ElaprDocPrgrsPickerConfirmPayload,
|
||||
ElaprDocPrgrsPickerDialog,
|
||||
type ElaprTmpltPickerConfirmPayload,
|
||||
ElaprTmpltPickerDialog,
|
||||
hasCurrentUserPendingApprovalRequest,
|
||||
@@ -39,6 +47,7 @@ import {
|
||||
import { SplmntDmndDialog, type SplmntDmndDialogConfirmPayload } from "@/widgets/pbadms-sprt";
|
||||
import { Button } from "@common/components";
|
||||
|
||||
import { isRagIndexingInProgress } from "../../lib/ai-pending-utils";
|
||||
import {
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION,
|
||||
type FnrsCnsltnViewButtonAction,
|
||||
@@ -61,6 +70,15 @@ import { FileVrfcSplmntDialog } from "./file-vrfc-splmnt-dialog";
|
||||
import { PwrkCnsltnDocMngDialog } from "./pwrk-cnsltn-doc-mng-dialog";
|
||||
import { PwrkCnsltnRvwRsltInqDialog } from "./pwrk-cnsltn-rvw-rslt-inq-dialog";
|
||||
|
||||
const AI_RETRY_PRGRS_STTS = new Set(["ER", "TE", "EV"]);
|
||||
|
||||
function findLatestByAutoSe(
|
||||
list: AiAutoPrcsLatestResponseDto[] | undefined,
|
||||
atmtnSeComCd: string,
|
||||
): AiAutoPrcsLatestResponseDto | undefined {
|
||||
return list?.find((item) => item.atmtnSeComCd === atmtnSeComCd);
|
||||
}
|
||||
|
||||
interface FloatingActionButtonsProps {
|
||||
data: FnrsCnsltnMngDetResponseDto;
|
||||
docSeComCd: string;
|
||||
@@ -77,6 +95,7 @@ interface FloatingActionButtonsProps {
|
||||
|
||||
type ActiveDialog =
|
||||
| "elaprTmplt"
|
||||
| "elaprDocPrgrs"
|
||||
| "rjct"
|
||||
| "dmndSplmnt"
|
||||
| "fileSplmnt"
|
||||
@@ -110,42 +129,86 @@ function FloatingActionButtons({
|
||||
const isRegister = data.detDto?.rgtr === userInfo?.userUnqId;
|
||||
|
||||
const needsAtrzResolve = needsRoleUser01AtrzResolve(dtlInstCd, prgrsSttsComCd);
|
||||
const needsTeApprovalCheck = needsAtrzResolve && prgrsSttsComCd === "TE";
|
||||
const needsPwrkDraftCheck = dtlInstCd === "ROLE_USER01" && ["TE", "RK"].includes(prgrsSttsComCd);
|
||||
const needsElaprPrgrsCheck = needsAtrzResolve && ["TE", "RK"].includes(prgrsSttsComCd);
|
||||
const needsPwrkDraftCheck =
|
||||
dtlInstCd === "ROLE_USER01" && ["SL", "TE", "RK"].includes(prgrsSttsComCd);
|
||||
const needsPwrkForRb = dtlInstCd === "ROLE_USER05" && prgrsSttsComCd === "RB";
|
||||
const needsPwrkLookup = needsPwrkDraftCheck || needsPwrkForRb;
|
||||
const needsPwrkForAiRetry = dtlInstCd === "ROLE_USER01" && ["TE", "EV"].includes(prgrsSttsComCd);
|
||||
const needsPwrkLookup = needsPwrkDraftCheck || needsPwrkForRb || needsPwrkForAiRetry;
|
||||
const needsAiAutoLatest = dtlInstCd === "ROLE_USER01" && AI_RETRY_PRGRS_STTS.has(prgrsSttsComCd);
|
||||
|
||||
const { data: elaprDocPrgrsList = [], isLoading: isElaprDocPrgrsLoading } =
|
||||
useElaprDocPrgrsByDocNo(needsTeApprovalCheck ? docNo : null);
|
||||
useElaprDocPrgrsByDocNo(needsElaprPrgrsCheck ? docNo : null);
|
||||
|
||||
const { data: pwrkCnsltnData, isLoading: isPwrkCnsltnLoading } = usePwrkCnsltnMngByUpDocNo(
|
||||
docNo,
|
||||
needsPwrkLookup,
|
||||
);
|
||||
const hasCompletedDraft = Boolean(findCompletedPwrkDraft(pwrkCnsltnData));
|
||||
const pwrkCnsltnDocNo = pwrkCnsltnData?.pwrkCnsltnDocNo;
|
||||
|
||||
const { data: aiAutoLatestList, isLoading: isAiAutoLatestLoading } = useAiAutoPrcsLatest(docNo, {
|
||||
enabled: needsAiAutoLatest,
|
||||
});
|
||||
|
||||
const fileVrfcLatest = findLatestByAutoSe(aiAutoLatestList, AI_AUTO_SE.FILE_VRFC);
|
||||
const draftGenLatest = findLatestByAutoSe(aiAutoLatestList, AI_AUTO_SE.DRAFT_GEN);
|
||||
const costVrfcLatest = findLatestByAutoSe(aiAutoLatestList, AI_AUTO_SE.COST_VRFC);
|
||||
const canRetryFileVrfc = fileVrfcLatest?.isRetryable === true;
|
||||
const canRetryDraftGen = draftGenLatest?.isRetryable === true;
|
||||
const canRetryCostVrfc = costVrfcLatest?.isRetryable === true;
|
||||
|
||||
const reTransferDocument = useReTransferDocument();
|
||||
const reGenerateDraft = useReGenerateDraft();
|
||||
const reVerifyCost = useReVerifyCost();
|
||||
const isBusy =
|
||||
isProcessing ||
|
||||
reTransferDocument.isPending ||
|
||||
reGenerateDraft.isPending ||
|
||||
reVerifyCost.isPending;
|
||||
|
||||
const hasCompletedDraft = Boolean(findCompletedPwrkDraft(pwrkCnsltnData));
|
||||
const pwrkCnsltnDocNo =
|
||||
pwrkCnsltnData?.pwrkCnsltnDocNo ??
|
||||
draftGenLatest?.pwrkCnsltnDocNo ??
|
||||
costVrfcLatest?.pwrkCnsltnDocNo;
|
||||
|
||||
const elaprPrgrsForPending =
|
||||
prgrsSttsComCd === "RK"
|
||||
? elaprDocPrgrsList.filter((row) => Number(row.elaprCycl) === 2)
|
||||
: elaprDocPrgrsList;
|
||||
const hasPendingApprovalRequest = hasCurrentUserPendingApprovalRequest(
|
||||
elaprDocPrgrsList,
|
||||
elaprPrgrsForPending,
|
||||
userInfo?.userUnqId,
|
||||
);
|
||||
const viewOnlyPwrkCnsltnDoc =
|
||||
prgrsSttsComCd === "SL" || (prgrsSttsComCd === "RB" && dtlInstCd !== "ROLE_USER05");
|
||||
|
||||
const actionButtons = useMemo(() => {
|
||||
if (needsAtrzResolve && isRvwLoginUserLoading) return [];
|
||||
if (needsTeApprovalCheck && isElaprDocPrgrsLoading) return [];
|
||||
if (needsElaprPrgrsCheck && isElaprDocPrgrsLoading) return [];
|
||||
if (needsPwrkDraftCheck && isPwrkCnsltnLoading) return [];
|
||||
if (needsAiAutoLatest && isAiAutoLatestLoading) return [];
|
||||
return getButtonConfigs(dtlInstCd, prgrsSttsComCd, atrzSeComCdList, {
|
||||
hasPendingApprovalRequest: needsTeApprovalCheck ? hasPendingApprovalRequest : undefined,
|
||||
hasPendingApprovalRequest: needsElaprPrgrsCheck ? hasPendingApprovalRequest : undefined,
|
||||
hasCompletedDraft: needsPwrkDraftCheck ? hasCompletedDraft : undefined,
|
||||
canRetryFileVrfc: needsAiAutoLatest ? canRetryFileVrfc : undefined,
|
||||
canRetryDraftGen: needsAiAutoLatest ? canRetryDraftGen : undefined,
|
||||
canRetryCostVrfc: needsAiAutoLatest ? canRetryCostVrfc : undefined,
|
||||
});
|
||||
}, [
|
||||
atrzSeComCdList,
|
||||
canRetryCostVrfc,
|
||||
canRetryDraftGen,
|
||||
canRetryFileVrfc,
|
||||
dtlInstCd,
|
||||
hasCompletedDraft,
|
||||
hasPendingApprovalRequest,
|
||||
isAiAutoLatestLoading,
|
||||
isElaprDocPrgrsLoading,
|
||||
isPwrkCnsltnLoading,
|
||||
isRvwLoginUserLoading,
|
||||
needsAiAutoLatest,
|
||||
needsAtrzResolve,
|
||||
needsTeApprovalCheck,
|
||||
needsElaprPrgrsCheck,
|
||||
needsPwrkDraftCheck,
|
||||
prgrsSttsComCd,
|
||||
]);
|
||||
@@ -252,19 +315,6 @@ function FloatingActionButtons({
|
||||
router.push("/fnrs-cnsltn");
|
||||
}
|
||||
|
||||
async function handleFnrsCnsltnAncmntClick() {
|
||||
const payload: DocPrgrsUpdateRequestDto = {
|
||||
docNo,
|
||||
docSeComCd,
|
||||
prgrsSttsComCd: "TB",
|
||||
};
|
||||
await executeAction({
|
||||
action: FNRS_CNSLTN_VIEW_BUTTON_ACTION.fnrsCnsltnAncmnt,
|
||||
request: payload,
|
||||
successMessage: "고시되었습니다.",
|
||||
});
|
||||
}
|
||||
|
||||
async function handleEnvoSbmsnClick() {
|
||||
const payload: DocPrgrsUpdateRequestDto = {
|
||||
docNo,
|
||||
@@ -278,19 +328,6 @@ function FloatingActionButtons({
|
||||
});
|
||||
}
|
||||
|
||||
async function handleLclRplyClick() {
|
||||
const payload: DocPrgrsUpdateRequestDto = {
|
||||
docNo,
|
||||
docSeComCd,
|
||||
prgrsSttsComCd: "RB",
|
||||
};
|
||||
await executeAction({
|
||||
action: FNRS_CNSLTN_VIEW_BUTTON_ACTION.lclRply,
|
||||
request: payload,
|
||||
successMessage: "지자체 회신이 완료되었습니다.",
|
||||
});
|
||||
}
|
||||
|
||||
async function handleRjctClick() {
|
||||
const payload: DocPrgrsUpdateRequestDto = {
|
||||
docNo,
|
||||
@@ -313,7 +350,7 @@ function FloatingActionButtons({
|
||||
await executeAction({
|
||||
action: FNRS_CNSLTN_VIEW_BUTTON_ACTION.sncnAprv,
|
||||
request: payload,
|
||||
successMessage: "인가승인되었습니다.",
|
||||
successMessage: "결과 회신이 완료되었습니다.",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -329,7 +366,7 @@ function FloatingActionButtons({
|
||||
);
|
||||
|
||||
if (percent !== 100) {
|
||||
alert("모든 체크리스트 항목을 체크해야 검토결과 회신이 가능합니다.");
|
||||
alert("모든 체크리스트 항목을 체크해야 검토 결과 회신이 가능합니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -337,7 +374,7 @@ function FloatingActionButtons({
|
||||
data,
|
||||
checklistFormState,
|
||||
"ENV",
|
||||
"RM",
|
||||
"EP",
|
||||
"CP",
|
||||
getReviewOpinionFormData(),
|
||||
opnrNm,
|
||||
@@ -346,7 +383,37 @@ function FloatingActionButtons({
|
||||
await executeAction({
|
||||
action: FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwRply,
|
||||
request: payload,
|
||||
successMessage: "재원협의 결과 회신이 완료되었습니다.",
|
||||
successMessage: "검토 결과 회신이 완료되었습니다.",
|
||||
});
|
||||
techReviewOpinionRef?.current?.resetForm();
|
||||
}
|
||||
|
||||
async function handleRvwOpnnRplyClick() {
|
||||
const { percent } = computeReviewStepChecklistProgress(
|
||||
data.cklstResultList ?? [],
|
||||
checklistFormState.results,
|
||||
"ENV",
|
||||
);
|
||||
|
||||
if (percent !== 100) {
|
||||
alert("모든 체크리스트 항목을 체크해야 검토 의견 회신이 가능합니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = buildTechReviewReplyPayload(
|
||||
data,
|
||||
checklistFormState,
|
||||
"ENV",
|
||||
"RB",
|
||||
"CP",
|
||||
getReviewOpinionFormData(),
|
||||
opnrNm,
|
||||
);
|
||||
|
||||
await executeAction({
|
||||
action: FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwOpnnRply,
|
||||
request: payload,
|
||||
successMessage: "검토 의견 회신이 완료되었습니다.",
|
||||
});
|
||||
techReviewOpinionRef?.current?.resetForm();
|
||||
}
|
||||
@@ -358,7 +425,7 @@ function FloatingActionButtons({
|
||||
await executeAction({
|
||||
action: FNRS_CNSLTN_VIEW_BUTTON_ACTION.rqstTechRvw,
|
||||
request: payload,
|
||||
successMessage: "기술검토 의뢰가 완료되었습니다.",
|
||||
successMessage: "기술 검토 의뢰가 완료되었습니다.",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -367,7 +434,7 @@ function FloatingActionButtons({
|
||||
await executeAction({
|
||||
action: FNRS_CNSLTN_VIEW_BUTTON_ACTION.rqstActnRvw,
|
||||
request: payload,
|
||||
successMessage: "조치결과 검토의뢰가 완료되었습니다.",
|
||||
successMessage: "조치 결과 검토 의뢰가 완료되었습니다.",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -376,7 +443,7 @@ function FloatingActionButtons({
|
||||
elaprDocNo: docNo,
|
||||
dmndTtl: data.detDto?.bizNm ?? "",
|
||||
dmndCn: data.detDto?.bizCn ?? "",
|
||||
elaprCycl: data.detDto?.prgrsSttsComCd === "RK" ? 2 : 1,
|
||||
elaprCycl: 1,
|
||||
docPrgrsSttsComCd: "AC",
|
||||
elaprPrgrsSttsList: buildElaprPrgrsSttsListFromAplnRows(aplnRows),
|
||||
};
|
||||
@@ -390,6 +457,25 @@ function FloatingActionButtons({
|
||||
});
|
||||
}
|
||||
|
||||
async function handleElaprDocPrgrsConfirm({ aplnRows }: ElaprDocPrgrsPickerConfirmPayload) {
|
||||
const payload: ElaprDmndSaveRequestDto = {
|
||||
elaprDocNo: docNo,
|
||||
dmndTtl: data.detDto?.bizNm ?? "",
|
||||
dmndCn: data.detDto?.bizCn ?? "",
|
||||
elaprCycl: 2,
|
||||
docPrgrsSttsComCd: "RK",
|
||||
elaprPrgrsSttsList: buildElaprPrgrsSttsListFromAplnRows(aplnRows),
|
||||
};
|
||||
await executeAction({
|
||||
action: FNRS_CNSLTN_VIEW_BUTTON_ACTION.dsgntRvwr,
|
||||
request: payload,
|
||||
successMessage: "검토자가 지정되었습니다.",
|
||||
});
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: [ENDPOINT.AS_ELAPR_SERVICE.ELAPR_DMND, "findDocPrgrsByDocNo", docNo],
|
||||
});
|
||||
}
|
||||
|
||||
async function handleRvwStrgClick() {
|
||||
const payload = buildTechReviewReplyPayload(
|
||||
data,
|
||||
@@ -410,15 +496,17 @@ function FloatingActionButtons({
|
||||
}
|
||||
|
||||
async function handleRvwCmptnClick() {
|
||||
const { percent } = computeReviewStepChecklistProgress(
|
||||
data.cklstResultList ?? [],
|
||||
checklistFormState.results,
|
||||
"KECO",
|
||||
);
|
||||
if (prgrsSttsComCd === "TE") {
|
||||
const { percent } = computeReviewStepChecklistProgress(
|
||||
data.cklstResultList ?? [],
|
||||
checklistFormState.results,
|
||||
"KECO",
|
||||
);
|
||||
|
||||
if (percent !== 100) {
|
||||
alert("모든 체크리스트 항목을 체크해야 검토완료가 가능합니다.");
|
||||
return;
|
||||
if (percent !== 100) {
|
||||
alert("모든 체크리스트 항목을 체크해야 검토완료가 가능합니다.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const payload = {
|
||||
@@ -426,7 +514,7 @@ function FloatingActionButtons({
|
||||
data,
|
||||
checklistFormState,
|
||||
"KECO",
|
||||
"TE",
|
||||
prgrsSttsComCd,
|
||||
"SV",
|
||||
getReviewOpinionFormData(),
|
||||
opnrNm,
|
||||
@@ -442,17 +530,13 @@ function FloatingActionButtons({
|
||||
setActiveDialog("elaprApproval");
|
||||
}
|
||||
|
||||
function handleDmndSplmntClick() {
|
||||
setActiveDialog("dmndSplmnt");
|
||||
}
|
||||
|
||||
async function handleDmndSplmntConfirm(payload: SplmntDmndDialogConfirmPayload) {
|
||||
setActiveDialog(null);
|
||||
const requestPayload = buildSupplementReqPayload(data, payload, checklistFormState);
|
||||
await executeAction({
|
||||
action: FNRS_CNSLTN_VIEW_BUTTON_ACTION.dmndSplmnt,
|
||||
request: requestPayload,
|
||||
successMessage: "보완요청이 처리되었습니다.",
|
||||
successMessage: "보완 요청이 처리되었습니다.",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -466,7 +550,7 @@ function FloatingActionButtons({
|
||||
await executeAction({
|
||||
action: FNRS_CNSLTN_VIEW_BUTTON_ACTION.fileSplmnt,
|
||||
request: requestPayload,
|
||||
successMessage: "파일보완 요청이 처리되었습니다.",
|
||||
successMessage: "파일 보완 요청이 처리되었습니다.",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -476,7 +560,7 @@ function FloatingActionButtons({
|
||||
await executeAction({
|
||||
action: FNRS_CNSLTN_VIEW_BUTTON_ACTION.dmndSplmnt,
|
||||
request: requestPayload,
|
||||
successMessage: "보완요청이 처리되었습니다.",
|
||||
successMessage: "보완 요청이 처리되었습니다.",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -496,17 +580,60 @@ function FloatingActionButtons({
|
||||
});
|
||||
}
|
||||
|
||||
function handleFileVrfcClick() {
|
||||
reTransferDocument.mutate(
|
||||
{ fnrsCnsltnDocNo: docNo },
|
||||
{ onSuccess: () => alert("파일 검증을 요청했습니다.") },
|
||||
);
|
||||
}
|
||||
|
||||
function handlePwrkCnsltnDocCrtClick() {
|
||||
if (!pwrkCnsltnDocNo) {
|
||||
alert("실무협의 문서번호를 찾을 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
reGenerateDraft.mutate(
|
||||
{
|
||||
fnrsCnsltnDocNo: docNo,
|
||||
pwrkCnsltnDocNo,
|
||||
crtDmndUuid: draftGenLatest?.crtDmndUuid,
|
||||
},
|
||||
{ onSuccess: () => alert("초안 생성을 요청했습니다.") },
|
||||
);
|
||||
}
|
||||
|
||||
function handleCostVrfcClick() {
|
||||
if (!pwrkCnsltnDocNo) {
|
||||
alert("실무협의 문서번호를 찾을 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
reVerifyCost.mutate(
|
||||
{
|
||||
fnrsCnsltnDocNo: docNo,
|
||||
pwrkCnsltnDocNo,
|
||||
},
|
||||
{ onSuccess: () => alert("금액 검증을 요청했습니다.") },
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="sticky bottom-0 z-40 !-mb-2 border-t bg-white pb-8 pt-6 shadow-[0_-4px_12px_rgba(0,0,0,0.06)]">
|
||||
<div className="mx-4 flex flex-wrap justify-center gap-3 sm:gap-4">
|
||||
<Button color="white" type="button" onClick={handleGoToList} disabled={isProcessing}>
|
||||
<Button color="white" type="button" onClick={handleGoToList} disabled={isBusy}>
|
||||
목록
|
||||
</Button>
|
||||
{!isAiPending && (
|
||||
<>
|
||||
{shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocCrt) && (
|
||||
<Button color="orange" onClick={() => {}} disabled={isProcessing}>
|
||||
<Button
|
||||
color="orange"
|
||||
onClick={handlePwrkCnsltnDocCrtClick}
|
||||
disabled={isBusy}
|
||||
title={draftGenLatest?.prcsErrCn ?? undefined}
|
||||
>
|
||||
실무협의서 초안 생성
|
||||
</Button>
|
||||
)}
|
||||
@@ -514,118 +641,137 @@ function FloatingActionButtons({
|
||||
<Button
|
||||
color="orange"
|
||||
onClick={() => setActiveDialog("pwrkCnsltnDocMng")}
|
||||
disabled={isProcessing}
|
||||
disabled={isBusy}
|
||||
>
|
||||
실무협의서 관리
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{canEdit && (
|
||||
<Button color="gray" onClick={handleEdit} disabled={isProcessing}>
|
||||
<Button color="gray" onClick={handleEdit} disabled={isBusy}>
|
||||
수정
|
||||
</Button>
|
||||
)}
|
||||
{shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.strg) && (
|
||||
<Button onClick={handleStrgClick} disabled={isProcessing}>
|
||||
<Button onClick={handleStrgClick} disabled={isBusy}>
|
||||
저장
|
||||
</Button>
|
||||
)}
|
||||
{shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.aply) && (
|
||||
<Button color="green" onClick={handleAplyClick} disabled={isProcessing}>
|
||||
<Button color="green" onClick={handleAplyClick} disabled={isBusy}>
|
||||
신청 완료
|
||||
</Button>
|
||||
)}
|
||||
{shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.del) && (
|
||||
<Button color="red" onClick={handleDelClick} disabled={isProcessing}>
|
||||
<Button color="red" onClick={handleDelClick} disabled={isBusy}>
|
||||
삭제
|
||||
</Button>
|
||||
)}
|
||||
{shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.fnrsCnsltnAncmnt) && (
|
||||
<Button onClick={handleFnrsCnsltnAncmntClick} disabled={isProcessing}>
|
||||
재원협의 고시
|
||||
</Button>
|
||||
)}
|
||||
{shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.envoSbmsn) && (
|
||||
<Button onClick={handleEnvoSbmsnClick} disabled={isProcessing}>
|
||||
<Button onClick={handleEnvoSbmsnClick} disabled={isBusy}>
|
||||
환경청 제출
|
||||
</Button>
|
||||
)}
|
||||
{shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.lclRply) && (
|
||||
<Button onClick={handleLclRplyClick} disabled={isProcessing}>
|
||||
지자체 회신
|
||||
</Button>
|
||||
)}
|
||||
{shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.rjct) && (
|
||||
<Button color="red" onClick={handleRjctClick} disabled={isProcessing}>
|
||||
<Button color="red" onClick={handleRjctClick} disabled={isBusy}>
|
||||
반려
|
||||
</Button>
|
||||
)}
|
||||
{shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.sncnAprv) && (
|
||||
<Button onClick={handleSncnAprvClick} disabled={isProcessing}>
|
||||
인가승인
|
||||
<Button color="green" onClick={handleSncnAprvClick} disabled={isBusy}>
|
||||
결과 회신
|
||||
</Button>
|
||||
)}
|
||||
{shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwOpnnRply) && (
|
||||
<Button onClick={handleRvwOpnnRplyClick} disabled={isBusy}>
|
||||
검토 의견 회신
|
||||
</Button>
|
||||
)}
|
||||
{shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwRply) && (
|
||||
<Button color="green" onClick={handleRvwRplyClick} disabled={isProcessing}>
|
||||
재원협의 결과 회신
|
||||
<Button color="green" onClick={handleRvwRplyClick} disabled={isBusy}>
|
||||
검토 결과 회신
|
||||
</Button>
|
||||
)}
|
||||
{shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.rqstTechRvw) && (
|
||||
<Button onClick={handleRqstTechRvwClick} disabled={isProcessing}>
|
||||
<Button onClick={handleRqstTechRvwClick} disabled={isBusy}>
|
||||
기술 검토 의뢰
|
||||
</Button>
|
||||
)}
|
||||
{shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.rqstActnRvw) && (
|
||||
<Button onClick={handleRqstActnRvwClick} disabled={isProcessing}>
|
||||
조치결과 검토 의뢰
|
||||
<Button onClick={handleRqstActnRvwClick} disabled={isBusy}>
|
||||
조치 결과 검토 의뢰
|
||||
</Button>
|
||||
)}
|
||||
{shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.fileSplmnt) && (
|
||||
<Button color="gray" onClick={handleFileSplmntClick} disabled={isProcessing}>
|
||||
<Button color="gray" onClick={handleFileSplmntClick} disabled={isBusy}>
|
||||
파일 보완 요청
|
||||
</Button>
|
||||
)}
|
||||
{shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.dsgntRvwr) && (
|
||||
<Button onClick={() => setActiveDialog("elaprTmplt")} disabled={isProcessing}>
|
||||
검토자 지정
|
||||
{shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.fileVrfc) && (
|
||||
<Button
|
||||
onClick={handleFileVrfcClick}
|
||||
disabled={isBusy}
|
||||
title={fileVrfcLatest?.prcsErrCn ?? undefined}
|
||||
>
|
||||
파일 검증
|
||||
</Button>
|
||||
)}
|
||||
{shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.dsgntRvwr) &&
|
||||
!isRagIndexingInProgress(prgrsSttsComCd, data.ragIndexingInfo) && (
|
||||
<Button
|
||||
onClick={() =>
|
||||
setActiveDialog(prgrsSttsComCd === "EV" ? "elaprDocPrgrs" : "elaprTmplt")
|
||||
}
|
||||
disabled={isBusy}
|
||||
>
|
||||
검토자 지정
|
||||
</Button>
|
||||
)}
|
||||
{shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.costVrfc) && (
|
||||
<Button
|
||||
onClick={handleCostVrfcClick}
|
||||
disabled={isBusy}
|
||||
title={costVrfcLatest?.prcsErrCn ?? undefined}
|
||||
>
|
||||
금액 검증
|
||||
</Button>
|
||||
)}
|
||||
{shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.actnCmptn) && (
|
||||
<Button color="green" onClick={handleActnCmptnClick} disabled={isProcessing}>
|
||||
<Button color="green" onClick={handleActnCmptnClick} disabled={isBusy}>
|
||||
조치 완료
|
||||
</Button>
|
||||
)}
|
||||
{shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwRsltInq) && (
|
||||
<Button
|
||||
color="sky"
|
||||
onClick={() => setActiveDialog("rvwRsltInq")}
|
||||
disabled={isProcessing}
|
||||
>
|
||||
<Button color="sky" onClick={() => setActiveDialog("rvwRsltInq")} disabled={isBusy}>
|
||||
검토 결과 조회
|
||||
</Button>
|
||||
)}
|
||||
{shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwStrg) && (
|
||||
<Button onClick={handleRvwStrgClick} disabled={isProcessing}>
|
||||
<Button onClick={handleRvwStrgClick} disabled={isBusy}>
|
||||
저장
|
||||
</Button>
|
||||
)}
|
||||
{shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwCmptn) && (
|
||||
<Button color="green" onClick={handleRvwCmptnClick} disabled={isProcessing}>
|
||||
<Button color="green" onClick={handleRvwCmptnClick} disabled={isBusy}>
|
||||
검토 완료
|
||||
</Button>
|
||||
)}
|
||||
{shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.dmndSplmnt) && (
|
||||
<Button color="gray" onClick={handleDmndSplmntClick} disabled={isProcessing}>
|
||||
<Button
|
||||
color="gray"
|
||||
onClick={() => setActiveDialog("dmndSplmnt")}
|
||||
disabled={isBusy}
|
||||
>
|
||||
보완 요청
|
||||
</Button>
|
||||
)}
|
||||
{shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.atrzAprv) && (
|
||||
<Button onClick={() => setActiveDialog("elaprApproval")} disabled={isProcessing}>
|
||||
<Button onClick={() => setActiveDialog("elaprApproval")} disabled={isBusy}>
|
||||
결재(승인)
|
||||
</Button>
|
||||
)}
|
||||
{shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.atrzRply) && (
|
||||
<Button onClick={() => setActiveDialog("elaprApproval")} disabled={isProcessing}>
|
||||
<Button onClick={() => setActiveDialog("elaprApproval")} disabled={isBusy}>
|
||||
결재(회신)
|
||||
</Button>
|
||||
)}
|
||||
@@ -643,18 +789,25 @@ function FloatingActionButtons({
|
||||
onConfirm={handleElaprTmpltConfirm}
|
||||
/>
|
||||
|
||||
<ElaprDocPrgrsPickerDialog
|
||||
open={activeDialog === "elaprDocPrgrs"}
|
||||
elaprDocNo={docNo}
|
||||
onClose={() => setActiveDialog(null)}
|
||||
onConfirm={handleElaprDocPrgrsConfirm}
|
||||
/>
|
||||
|
||||
<SplmntDmndDialog
|
||||
splmntArtclUpCd="SPLMNTARTCL"
|
||||
open={activeDialog === "dmndSplmnt"}
|
||||
onOpenChange={() => setActiveDialog(null)}
|
||||
isSubmitting={isProcessing}
|
||||
isSubmitting={isBusy}
|
||||
onConfirm={handleDmndSplmntConfirm}
|
||||
/>
|
||||
|
||||
<FileVrfcSplmntDialog
|
||||
open={activeDialog === "fileSplmnt"}
|
||||
fnrsCnsltnDocNo={docNo}
|
||||
isSubmitting={isProcessing}
|
||||
isSubmitting={isBusy}
|
||||
onClose={() => setActiveDialog(null)}
|
||||
onConfirm={handleFileSplmntConfirm}
|
||||
/>
|
||||
@@ -664,6 +817,7 @@ function FloatingActionButtons({
|
||||
onOpenChange={(open) => setActiveDialog(open ? "elaprApproval" : null)}
|
||||
elaprDocNo={data.detDto?.fnrsCnsltnDocNo ?? fnrsCnsltnDocNo}
|
||||
currentUserUuid={userInfo?.userUnqId ?? ""}
|
||||
elaprCycl={prgrsSttsComCd === "RK" ? 2 : undefined}
|
||||
onSuccess={() => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: fnrsCnsltnKeys.detail(fnrsCnsltnDocNo),
|
||||
@@ -675,14 +829,19 @@ function FloatingActionButtons({
|
||||
open={activeDialog === "pwrkCnsltnDocMng"}
|
||||
fnrsCnsltnDocNo={docNo}
|
||||
atrzSeComCdList={atrzSeComCdList}
|
||||
canSave={prgrsSttsComCd === "RA" ? false : undefined}
|
||||
prgrsSttsComCd={prgrsSttsComCd}
|
||||
canSave={
|
||||
(needsElaprPrgrsCheck && !hasPendingApprovalRequest) || viewOnlyPwrkCnsltnDoc
|
||||
? false
|
||||
: null
|
||||
}
|
||||
onClose={() => setActiveDialog(null)}
|
||||
/>
|
||||
|
||||
<PwrkCnsltnRvwRsltInqDialog
|
||||
open={activeDialog === "rvwRsltInq"}
|
||||
fnrsCnsltnDocNo={docNo}
|
||||
isSubmitting={isProcessing}
|
||||
isSubmitting={isBusy}
|
||||
onClose={() => setActiveDialog(null)}
|
||||
onConfirm={handleRvwRsltSplmntConfirm}
|
||||
/>
|
||||
@@ -806,28 +965,18 @@ async function handleActnCmptn(
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAtrzAprv(context: ActionHandlerContext): Promise<void> {
|
||||
// TODO: 결재 승인 처리 API 호출
|
||||
context.onSuccess?.();
|
||||
}
|
||||
|
||||
async function handleAtrzRply(context: ActionHandlerContext): Promise<void> {
|
||||
// TODO: 결재 회신 처리 API 호출
|
||||
context.onSuccess?.();
|
||||
}
|
||||
|
||||
const buttonActionHandlers: Record<FnrsCnsltnViewButtonAction, ActionHandler> = {
|
||||
strg: handleSaveApply,
|
||||
aply: handleSaveApply,
|
||||
del: handleDel,
|
||||
fnrsCnsltnAncmnt: handleChangePrgrsSttsComCd,
|
||||
envoSbmsn: handleChangePrgrsSttsComCd,
|
||||
rjct: handleChangePrgrsSttsComCd,
|
||||
sncnAprv: handleChangePrgrsSttsComCd,
|
||||
lclRply: handleChangePrgrsSttsComCd,
|
||||
rvwRply: handleTechReview,
|
||||
rvwOpnnRply: handleTechReview,
|
||||
rqstTechRvw: handleRequestExamine,
|
||||
fileSplmnt: handleDmndSplmnt,
|
||||
fileVrfc: () => Promise.resolve(),
|
||||
dsgntRvwr: handleDsgntRvwr,
|
||||
pwrkCnsltnDocCrt: () => Promise.resolve(),
|
||||
pwrkCnsltnDocMng: () => Promise.resolve(),
|
||||
@@ -835,10 +984,11 @@ const buttonActionHandlers: Record<FnrsCnsltnViewButtonAction, ActionHandler> =
|
||||
rvwStrg: handleTechReview,
|
||||
rvwCmptn: handleTechReview,
|
||||
dmndSplmnt: handleDmndSplmnt,
|
||||
atrzAprv: handleAtrzAprv,
|
||||
atrzRply: handleAtrzRply,
|
||||
atrzAprv: () => Promise.resolve(),
|
||||
atrzRply: () => Promise.resolve(),
|
||||
actnCmptn: handleActnCmptn,
|
||||
rqstActnRvw: handleRequestActnReview,
|
||||
costVrfc: () => Promise.resolve(),
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { PanelRightClose, PanelRightOpen } from "lucide-react";
|
||||
|
||||
import { getDefaultFileKey, useAlertStore, useTus } from "@/shared/lib";
|
||||
import { AttachmentFileField } from "@/shared/ui";
|
||||
import { attachService, parseFilenameFromResponse } from "@/entities/attach";
|
||||
import {
|
||||
attachService,
|
||||
parseFilenameFromResponse,
|
||||
useDownloadAttachmentByAtflId,
|
||||
} from "@/entities/attach";
|
||||
import {
|
||||
pwrkCnsltnDocCrtRsltSaveRequestSchema,
|
||||
pwrkCnsltnDocCrtRsltService,
|
||||
} from "@/entities/pwrk-cnsltn-doc-crt-rslt";
|
||||
import { usePwrkCnsltnDocPjtcoAjmtMngPage } from "@/entities/pwrk-cnsltn-doc-pjtco-ajmt-mng";
|
||||
import {
|
||||
findCompletedPwrkDraft,
|
||||
pwrkCnsltnMngKeys,
|
||||
@@ -27,6 +33,8 @@ import {
|
||||
Skeleton,
|
||||
} from "@common/components";
|
||||
|
||||
import { PwrkCnsltnDocPjtcoAjmtTable } from "./pwrk-cnsltn-doc-pjtco-ajmt-table";
|
||||
|
||||
const HangulGiangi = dynamic(
|
||||
() => import("@/widgets/hangulgiangi").then((module) => module.HangulGiangi),
|
||||
{
|
||||
@@ -39,11 +47,14 @@ const PWRK_ATCH_FILE_KEY_PREFIX = "pwrk-cnsltn-doc::";
|
||||
|
||||
const APPROVER_ATRZ_SE_COM_CDS = new Set(["ATRZ03", "ATRZ99"]);
|
||||
|
||||
const VIEW_ONLY_PRGRS_STTS_COM_CDS = new Set(["TP", "RA", "AV", "EV", "RR", "EP", "TA"]);
|
||||
|
||||
interface PwrkCnsltnDocMngDialogProps {
|
||||
open: boolean;
|
||||
fnrsCnsltnDocNo: string;
|
||||
atrzSeComCdList: string[];
|
||||
canSave?: boolean;
|
||||
atrzSeComCdList?: string[];
|
||||
prgrsSttsComCd?: string;
|
||||
canSave?: boolean | null;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
@@ -62,6 +73,7 @@ function PwrkCnsltnDocMngDialog({
|
||||
open,
|
||||
fnrsCnsltnDocNo,
|
||||
atrzSeComCdList,
|
||||
prgrsSttsComCd,
|
||||
canSave: canSaveProp,
|
||||
onClose,
|
||||
}: PwrkCnsltnDocMngDialogProps) {
|
||||
@@ -69,6 +81,8 @@ function PwrkCnsltnDocMngDialog({
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
const [atchFileGroupId, setAtchFileGroupId] = useState("");
|
||||
const [uploadedFiles, setUploadedFiles] = useState<UploadedAtchFile[]>([]);
|
||||
const [isPjtcoAjmtTableOpen, setIsPjtcoAjmtTableOpen] = useState(false);
|
||||
const { mutate: downloadByAtflId } = useDownloadAttachmentByAtflId();
|
||||
|
||||
const {
|
||||
data: pwrkCnsltnData,
|
||||
@@ -92,6 +106,20 @@ function PwrkCnsltnDocMngDialog({
|
||||
const documentFileName = `실무협의서_${pwrkCnsltnData?.pwrkCnsltnDocNo ?? crtDmndUuid}.hwpx`;
|
||||
const isLoading = isPwrkCnsltnLoading || isDocumentLoading;
|
||||
const isError = isPwrkCnsltnError || isDocumentError;
|
||||
const pjtcoAjmtSearchParams = useMemo(
|
||||
() => ({
|
||||
pwrkCnsltnDocNo: pwrkCnsltnData?.pwrkCnsltnDocNo,
|
||||
page: 0,
|
||||
size: 1000,
|
||||
sort: "rvwOpnnSn,asc",
|
||||
}),
|
||||
[pwrkCnsltnData?.pwrkCnsltnDocNo],
|
||||
);
|
||||
const {
|
||||
data: pjtcoAjmtPage,
|
||||
isLoading: isPjtcoAjmtLoading,
|
||||
isError: isPjtcoAjmtError,
|
||||
} = usePwrkCnsltnDocPjtcoAjmtMngPage(pjtcoAjmtSearchParams, open);
|
||||
|
||||
const { uploadFileAsync, isUploading, uploads } = useTus({
|
||||
onSuccess: (atflCd, atflId, _file, fileKey) => {
|
||||
@@ -234,8 +262,11 @@ function PwrkCnsltnDocMngDialog({
|
||||
}
|
||||
|
||||
const isAnyAtchUploading = uploadedFiles.some((item) => isUploading[item.fileKey]);
|
||||
const canSaveByAtrz = !atrzSeComCdList.some((code) => APPROVER_ATRZ_SE_COM_CDS.has(code));
|
||||
const canSave = canSaveProp ?? canSaveByAtrz;
|
||||
const viewOnly = prgrsSttsComCd ? VIEW_ONLY_PRGRS_STTS_COM_CDS.has(prgrsSttsComCd) : false;
|
||||
const canSaveByAtrz = atrzSeComCdList
|
||||
? !atrzSeComCdList.some((code) => APPROVER_ATRZ_SE_COM_CDS.has(code))
|
||||
: true;
|
||||
const canSave = !viewOnly && (canSaveProp ?? canSaveByAtrz);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||
@@ -258,17 +289,54 @@ function PwrkCnsltnDocMngDialog({
|
||||
생성 완료된 실무협의서 초안이 없습니다.
|
||||
</p>
|
||||
) : null}
|
||||
{completedDraft ? (
|
||||
documentBlob ? (
|
||||
<HangulGiangi
|
||||
documentBlob={documentBlob}
|
||||
documentKey={completedDraft.crtDmndUuid}
|
||||
documentFileName={documentFileName}
|
||||
canSave={canSave}
|
||||
onSaveDocument={handleSaveDocument}
|
||||
onDownload={handleDownload}
|
||||
/>
|
||||
) : null
|
||||
{completedDraft && documentBlob ? (
|
||||
<div className="flex min-h-[40rem] items-stretch gap-2">
|
||||
<div className="min-w-0 flex-1">
|
||||
<HangulGiangi
|
||||
documentBlob={documentBlob}
|
||||
documentKey={completedDraft.crtDmndUuid}
|
||||
documentFileName={documentFileName}
|
||||
canSave={canSave}
|
||||
onSaveDocument={handleSaveDocument}
|
||||
onDownload={handleDownload}
|
||||
/>
|
||||
</div>
|
||||
{pjtcoAjmtPage?.content?.length ? (
|
||||
isPjtcoAjmtTableOpen ? (
|
||||
<section className="flex h-[calc(64vh+4rem)] w-0 flex-[0.4] flex-col overflow-hidden rounded-md border border-border">
|
||||
<header className="flex items-center justify-between gap-2 border-b border-border px-3 py-2">
|
||||
<h3 className="text-xl font-semibold">금액검증결과</h3>
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-sm border border-border p-2 hover:opacity-90"
|
||||
onClick={() => setIsPjtcoAjmtTableOpen(false)}
|
||||
aria-label="접기"
|
||||
>
|
||||
<PanelRightClose className="size-6" />
|
||||
</button>
|
||||
</header>
|
||||
<div className="min-h-0 flex-1 overflow-y-auto">
|
||||
<PwrkCnsltnDocPjtcoAjmtTable
|
||||
rows={pjtcoAjmtPage?.content ?? []}
|
||||
isLoading={isPjtcoAjmtLoading}
|
||||
isError={isPjtcoAjmtError}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center gap-1 self-start rounded-sm border border-border px-1 py-2 hover:opacity-90"
|
||||
style={{ writingMode: "vertical-rl" }}
|
||||
onClick={() => setIsPjtcoAjmtTableOpen(true)}
|
||||
aria-label="펼치기"
|
||||
>
|
||||
<PanelRightOpen className="size-5" />
|
||||
<span className="text-xl tracking-wide">금액검증결과</span>
|
||||
</button>
|
||||
)
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{completedDraft ? (
|
||||
@@ -281,6 +349,7 @@ function PwrkCnsltnDocMngDialog({
|
||||
files={uploadedFiles.map((item) => ({
|
||||
fileKey: item.fileKey,
|
||||
fileName: item.fileName,
|
||||
onDownload: item.atflId ? () => downloadByAtflId(item.atflId!) : undefined,
|
||||
}))}
|
||||
inputId="pwrk-cnsltn-doc-atch-file-input"
|
||||
isUploadingByKey={isUploading}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
"use client";
|
||||
|
||||
import type { PwrkCnsltnDocPjtcoAjmtMngResponseDto } from "@/entities/pwrk-cnsltn-doc-pjtco-ajmt-mng";
|
||||
import { fmtNumber } from "@/widgets/pbadms-sprt";
|
||||
import {
|
||||
Skeleton,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@common/components";
|
||||
|
||||
interface PwrkCnsltnDocPjtcoAjmtTableProps {
|
||||
rows: PwrkCnsltnDocPjtcoAjmtMngResponseDto[];
|
||||
isLoading: boolean;
|
||||
isError: boolean;
|
||||
}
|
||||
|
||||
function PwrkCnsltnDocPjtcoAjmtTable({
|
||||
rows,
|
||||
isLoading,
|
||||
isError,
|
||||
}: PwrkCnsltnDocPjtcoAjmtTableProps) {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="space-y-2 p-3">
|
||||
<Skeleton className="h-10 w-full" />
|
||||
<Skeleton className="h-24 w-full" />
|
||||
<Skeleton className="h-24 w-full" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<p className="px-3 py-10 text-center text-muted-foreground">
|
||||
사업비조정 정보를 불러오지 못했습니다.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Table variant="secondary">
|
||||
<colgroup>
|
||||
<col width="30%" />
|
||||
<col width="50%" />
|
||||
<col width="20%" />
|
||||
</colgroup>
|
||||
<TableHeader className="sticky top-0">
|
||||
<TableRow>
|
||||
<TableHead>구분</TableHead>
|
||||
<TableHead>검증결과</TableHead>
|
||||
<TableHead className="text-right">
|
||||
<div className="flex flex-col gap-1">
|
||||
<span>검증금액</span>
|
||||
<span className="text-sm">(단위: 백만 원)</span>
|
||||
</div>
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{rows.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={3} className="py-10 text-center text-muted-foreground">
|
||||
조회된 사업비조정 항목이 없습니다.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
rows.map((row) => (
|
||||
<TableRow key={getPjtcoAjmtRowKey(row)}>
|
||||
<TableCell>{formatDivision(row)}</TableCell>
|
||||
<TableCell>
|
||||
<p className="whitespace-pre-wrap">{formatVrfcCn(row.vrfcCn)}</p>
|
||||
</TableCell>
|
||||
<TableCell className="text-right">{formatVrfcAmt(row.vrfcTpcoAmt)}</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
|
||||
function getPjtcoAjmtRowKey(row: PwrkCnsltnDocPjtcoAjmtMngResponseDto) {
|
||||
return [row.pwrkCnsltnDocNo, row.rvwOpnnSn, row.acntgSeCd].join("::");
|
||||
}
|
||||
|
||||
function formatDivision(row: PwrkCnsltnDocPjtcoAjmtMngResponseDto) {
|
||||
const label = row.acntgSeCdNm?.trim() || row.acntgSeCd?.trim();
|
||||
return label || "-";
|
||||
}
|
||||
|
||||
function formatVrfcCn(value: string | undefined) {
|
||||
const text = value?.trim();
|
||||
return text || "-";
|
||||
}
|
||||
|
||||
function formatVrfcAmt(value: number | undefined) {
|
||||
if (value === undefined || value === null) return "-";
|
||||
const formatted = fmtNumber(value);
|
||||
return formatted || "-";
|
||||
}
|
||||
|
||||
export { PwrkCnsltnDocPjtcoAjmtTable };
|
||||
export type { PwrkCnsltnDocPjtcoAjmtTableProps };
|
||||
@@ -96,7 +96,7 @@ function PwrkCnsltnRvwRsltInqDialog({
|
||||
aria-describedby={undefined}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle>검토결과조회</DialogTitle>
|
||||
<DialogTitle>검토 결과 조회</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
{isLoading ? (
|
||||
@@ -159,9 +159,9 @@ function CnsltnListTable({
|
||||
return (
|
||||
<Table variant="secondary">
|
||||
<colgroup>
|
||||
<col width="8%" />
|
||||
<col width="42%" />
|
||||
<col width="50%" />
|
||||
<col width="4%" />
|
||||
<col width="4%" />
|
||||
<col width="92%" />
|
||||
</colgroup>
|
||||
<TableHeader className="sticky top-0">
|
||||
<TableRow>
|
||||
@@ -172,7 +172,7 @@ function CnsltnListTable({
|
||||
aria-label="전체 선택"
|
||||
/>
|
||||
</TableHead>
|
||||
<TableHead>분류</TableHead>
|
||||
<TableHead>구분</TableHead>
|
||||
<TableHead>검토의견</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
@@ -194,8 +194,8 @@ function CnsltnListTable({
|
||||
aria-label="행 선택"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<p className="whitespace-pre-wrap">{formatOptional(row.rmrk)}</p>
|
||||
<TableCell align="center">
|
||||
<p className="whitespace-pre-wrap">{formatOptional(row.rvwOpnnSn)}</p>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<p className="whitespace-pre-wrap">{formatOptional(row.rvwOpnn)}</p>
|
||||
|
||||
@@ -9,11 +9,10 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { SectionCollapsibleGroup, SectionCollapsibleToggle } from "@/shared/ui";
|
||||
import { RhfDevTool, SectionCollapsibleGroup, SectionCollapsibleToggle } from "@/shared/ui";
|
||||
import { useAcntgSeMngTree } from "@/entities/acntg";
|
||||
import { useEsntlAtchFileMngTree } from "@/entities/esntl";
|
||||
import {
|
||||
type FnrsCnsltnApplySaveRequestDto,
|
||||
fnrsCnsltnKeys,
|
||||
type FnrsCnsltnMngDetResponseDto,
|
||||
fnrsCnsltnMngService,
|
||||
@@ -32,21 +31,19 @@ import {
|
||||
isFormEsntlAtchComplete,
|
||||
PrivacyConsentSection,
|
||||
ProgressStatusSection,
|
||||
recalculateFnrsSharTotals,
|
||||
SplmntDmndFormSection,
|
||||
toFnrsSharSaveList,
|
||||
validateBizCstSharBalance,
|
||||
} from "@/widgets/pbadms-sprt";
|
||||
import { Button } from "@common/components";
|
||||
|
||||
import { type FnrsCnsltnRegistFormData, fnrsCnsltnRegistSchema } from "../lib/fnrs-cnsltn-regist-schema";
|
||||
import {
|
||||
type FnrsCnsltnRegistFormData,
|
||||
fnrsCnsltnRegistSchema,
|
||||
formatTaskSeComCdForApi,
|
||||
} from "../lib/fnrs-cnsltn-regist-schema";
|
||||
import { getCreateDefaultValues, mapDetailToFormData } from "../lib/form-data-mapper";
|
||||
buildFormPayload,
|
||||
getCreateDefaultValues,
|
||||
mapDetailToFormData,
|
||||
} from "../lib/form-data-mapper";
|
||||
import { PROGRESS_STATUS_COLUMNS } from "../model/columns";
|
||||
|
||||
import { PwrkCnsltnDocMngDialog } from "./components/pwrk-cnsltn-doc-mng-dialog";
|
||||
import { BasicInfoFormSection } from "./sections/basic-info-form-section";
|
||||
interface FnrsFormPageProps {
|
||||
mode: "create" | "edit";
|
||||
@@ -66,8 +63,10 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps)
|
||||
const userInfo = useUserStore((state) => state.userInfo);
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [isPwrkCnsltnDocMngOpen, setIsPwrkCnsltnDocMngOpen] = useState(false);
|
||||
const isEditMode = mode === "edit";
|
||||
const isSplmntPrgrsStts = initialData?.detDto?.prgrsSttsComCd === "SL";
|
||||
const fnrsCnsltnDocNo = initialData?.detDto?.fnrsCnsltnDocNo;
|
||||
|
||||
const { data: masterSharTree = [] } = useFnrsSharSeMngTree();
|
||||
const { data: acntgTree = [] } = useAcntgSeMngTree();
|
||||
@@ -86,54 +85,25 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps)
|
||||
: getCreateDefaultValues(userInfo, createDefaults),
|
||||
});
|
||||
|
||||
function buildFormPayload(
|
||||
data: FnrsCnsltnRegistFormData,
|
||||
prgrsSttsComCd: string,
|
||||
): FnrsCnsltnApplySaveRequestDto | undefined {
|
||||
const withTotals = recalculateFnrsSharTotals(data.fnrsSharList, sharItems);
|
||||
const validationMessage = validateBizCstSharBalance(
|
||||
data.bizCstList,
|
||||
withTotals,
|
||||
function createFormPayload(data: FnrsCnsltnRegistFormData, prgrsSttsComCd: string) {
|
||||
const result = buildFormPayload(data, prgrsSttsComCd, {
|
||||
isEditMode,
|
||||
docNo: initialData?.detDto?.fnrsCnsltnDocNo,
|
||||
sharItems,
|
||||
costItems,
|
||||
);
|
||||
if (validationMessage) {
|
||||
alert(validationMessage);
|
||||
});
|
||||
if ("validationMessage" in result) {
|
||||
alert(result.validationMessage);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { taskSeComCd: formTaskSeComCd, ...fnrsCnsltnMngRest } = data.fnrsCnsltnMng;
|
||||
|
||||
return {
|
||||
fnrsCnsltnMng: {
|
||||
...fnrsCnsltnMngRest,
|
||||
taskSeComCd: formatTaskSeComCdForApi(formTaskSeComCd),
|
||||
fnrsCnsltnDocNo: isEditMode ? initialData?.detDto?.fnrsCnsltnDocNo : undefined,
|
||||
prgrsSttsComCd,
|
||||
},
|
||||
bzScaleList: data.bzScaleList,
|
||||
bizCstList: data.bizCstList,
|
||||
fnrsSharList: toFnrsSharSaveList(withTotals),
|
||||
cklstResultList: data.cklstResultList.map((item) => ({
|
||||
...item,
|
||||
selected: item.selected ?? undefined,
|
||||
})),
|
||||
esntlAtchFileList: data.esntlAtchFileList,
|
||||
splmntDmndList: data.splmntDmndList.map(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
({ splmntAtchFileNm, splmntAtchFileAtflId, splmntDmndAtchFileNm, ...item }) => ({
|
||||
...item,
|
||||
...(prgrsSttsComCd !== "SV" && prgrsSttsComCd !== "ST" && { cmptnYn: "Y" }),
|
||||
}),
|
||||
),
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
async function submitForm(
|
||||
data: FnrsCnsltnRegistFormData,
|
||||
prgrsSttsComCd: "SV" | "ST",
|
||||
): Promise<string | undefined> {
|
||||
const payload = buildFormPayload(data, prgrsSttsComCd);
|
||||
const payload = createFormPayload(data, prgrsSttsComCd);
|
||||
if (!payload) return undefined;
|
||||
return fnrsCnsltnMngService.saveApply(payload);
|
||||
}
|
||||
@@ -143,7 +113,7 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps)
|
||||
if (!beforePrgrsSttsComCd) return undefined;
|
||||
|
||||
const docNo = initialData?.detDto?.fnrsCnsltnDocNo;
|
||||
const payload = buildFormPayload(data, beforePrgrsSttsComCd);
|
||||
const payload = createFormPayload(data, beforePrgrsSttsComCd);
|
||||
if (!docNo || !payload) return undefined;
|
||||
|
||||
return fnrsCnsltnMngService.submitSupplement(docNo, payload);
|
||||
@@ -156,7 +126,7 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps)
|
||||
(item) => !item.splmntCn || !item.splmntCn.trim(),
|
||||
);
|
||||
if (hasEmptySplmntCn) {
|
||||
alert("보완요청내용이 있습니다. 보완완료내용을 입력해 주세요.");
|
||||
alert("보완요청내용이 있습니다. 보완 완료내용을 입력해 주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -171,12 +141,12 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps)
|
||||
const result = await submitSupplementForm(data);
|
||||
if (!result) return;
|
||||
|
||||
alert("보완완료 처리되었습니다.");
|
||||
alert("보완 완료 처리되었습니다.");
|
||||
await queryClient.invalidateQueries({ queryKey: fnrsCnsltnKeys.all });
|
||||
router.push(`/fnrs-cnsltn/${docNo}`);
|
||||
} catch (error) {
|
||||
console.error("보완완료 실패:", error);
|
||||
alert("보완완료 처리 중 오류가 발생했습니다.");
|
||||
console.error("보완 완료 실패:", error);
|
||||
alert("보완 완료 처리 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
@@ -329,19 +299,40 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps)
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Button
|
||||
type="button"
|
||||
onClick={form.handleSubmit(onSplmntCmptn)}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
보완완료
|
||||
</Button>
|
||||
<>
|
||||
{fnrsCnsltnDocNo ? (
|
||||
<Button
|
||||
color="orange"
|
||||
type="button"
|
||||
onClick={() => setIsPwrkCnsltnDocMngOpen(true)}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
실무협의서 관리
|
||||
</Button>
|
||||
) : null}
|
||||
<Button
|
||||
type="button"
|
||||
onClick={form.handleSubmit(onSplmntCmptn)}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
보완 완료
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SectionCollapsibleGroup>
|
||||
</form>
|
||||
{fnrsCnsltnDocNo ? (
|
||||
<PwrkCnsltnDocMngDialog
|
||||
open={isPwrkCnsltnDocMngOpen}
|
||||
fnrsCnsltnDocNo={fnrsCnsltnDocNo}
|
||||
canSave
|
||||
onClose={() => setIsPwrkCnsltnDocMngOpen(false)}
|
||||
/>
|
||||
) : null}
|
||||
<RhfDevTool control={form.control} />
|
||||
</FormProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
} from "@/widgets/pbadms-sprt";
|
||||
import { Skeleton } from "@common/components";
|
||||
|
||||
import { getAiPendingMessage, isAiPendingStatus } from "../lib/ai-pending-utils";
|
||||
import { getAiPendingMessage, isAiPendingStatus, isRagIndexingInProgress } from "../lib/ai-pending-utils";
|
||||
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";
|
||||
@@ -95,20 +95,25 @@ function FnrsViewPage({ id }: FnrsViewPageProps) {
|
||||
detDto.prgrsSttsComCd || "",
|
||||
);
|
||||
const isAiPending = isAiPendingStatus(detDto.prgrsSttsComCd || "");
|
||||
const isRagIndexing = isRagIndexingInProgress(detDto.prgrsSttsComCd || "", data.ragIndexingInfo);
|
||||
const showPendingBanner = isAiPending || isRagIndexing;
|
||||
const pendingMessage = isRagIndexing
|
||||
? (data.ragIndexingInfo?.message ?? "")
|
||||
: getAiPendingMessage(detDto.prgrsSttsComCd || "");
|
||||
|
||||
return (
|
||||
<div className="mt-4">
|
||||
<SectionCollapsibleGroup>
|
||||
<div className="sticky top-[6rem] z-30 flex items-center gap-4 bg-container px-4 py-2">
|
||||
<SectionCollapsibleToggle />
|
||||
{isAiPending && (
|
||||
{showPendingBanner && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="relative flex size-3">
|
||||
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-primary opacity-75"></span>
|
||||
<span className="relative inline-flex size-3 rounded-full bg-primary"></span>
|
||||
</span>
|
||||
<TextShimmer active className="text-xl">
|
||||
{getAiPendingMessage(detDto.prgrsSttsComCd || "")}
|
||||
{pendingMessage}
|
||||
</TextShimmer>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
|
||||
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
|
||||
|
||||
import { Paperclip } from "lucide-react";
|
||||
|
||||
import { PROXY_HOST } from "@/shared/config";
|
||||
import { dayjs, getDefaultFileKey, useTus } from "@/shared/lib";
|
||||
import { AttachmentFileField, SectionCollapsible } from "@/shared/ui";
|
||||
import { Field, FieldLabel, FieldSet } from "@/shared/ui/field";
|
||||
import { attachService } from "@/entities/attach";
|
||||
import {
|
||||
AttachmentDownloadLink,
|
||||
attachService,
|
||||
useDownloadAttachmentByAtflId,
|
||||
} from "@/entities/attach";
|
||||
import type { FnrsCnsltnRvwOpnnResponseDto } from "@/entities/fnrs-cnsltn";
|
||||
import {
|
||||
RadioGroup,
|
||||
@@ -82,6 +83,7 @@ const TechReviewOpinionSection = forwardRef<
|
||||
opnrSeComCd: editableReviewStep ?? "",
|
||||
});
|
||||
const [uploadedFile, setUploadedFile] = useState<UploadedOpinionFile | null>(null);
|
||||
const { mutate: downloadByAtflId } = useDownloadAttachmentByAtflId();
|
||||
|
||||
useEffect(() => {
|
||||
setFormData((prev) => ({
|
||||
@@ -136,8 +138,7 @@ const TechReviewOpinionSection = forwardRef<
|
||||
const kecoRvwList = rvwList.filter((item) => item.opnrSeComCd === "KECO");
|
||||
|
||||
const hasEnvSection = envRvwList.length > 0 || editableReviewStep === "ENV";
|
||||
const hasKecoSection =
|
||||
kecoRvwList.length > 0 || (editableReviewStep === "KECO" && canWriteKecoOpinion);
|
||||
const hasKecoSection = kecoRvwList.length > 0 || editableReviewStep === "KECO";
|
||||
|
||||
if (!hasEnvSection && !hasKecoSection) {
|
||||
return null;
|
||||
@@ -247,7 +248,15 @@ const TechReviewOpinionSection = forwardRef<
|
||||
<AttachmentFileField
|
||||
files={
|
||||
uploadedFile
|
||||
? [{ fileKey: uploadedFile.fileKey, fileName: uploadedFile.fileName }]
|
||||
? [
|
||||
{
|
||||
fileKey: uploadedFile.fileKey,
|
||||
fileName: uploadedFile.fileName,
|
||||
onDownload: uploadedFile.atflId
|
||||
? () => downloadByAtflId(uploadedFile.atflId!)
|
||||
: undefined,
|
||||
},
|
||||
]
|
||||
: []
|
||||
}
|
||||
inputId={`rvw-opnn-${reviewStep}-file-input`}
|
||||
@@ -321,15 +330,10 @@ function TechReviewOpinionTable({ items }: TechReviewOpinionTableProps) {
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
{item.atchFileInfo?.atflCd ? (
|
||||
<a
|
||||
className="inline-flex cursor-pointer items-start gap-1 text-left text-blue-600 hover:underline"
|
||||
href={`${PROXY_HOST}/attach-service/api/v1/atflcd/download/${item.atchFileInfo.atflCd}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<Paperclip className="my-1 size-5 shrink-0" />
|
||||
{item.atchFileInfo.orgnlFileNm || "다운로드"}
|
||||
</a>
|
||||
<AttachmentDownloadLink
|
||||
atflCd={item.atchFileInfo.atflCd}
|
||||
fileName={item.atchFileInfo.orgnlFileNm || "다운로드"}
|
||||
/>
|
||||
) : (
|
||||
"-"
|
||||
)}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import type { IUserInfo } from "@/shared/store/user-store";
|
||||
import type { InstlSncnMngDetResponse } from "@/entities/instl-sncn";
|
||||
import {
|
||||
type InstlSncnApplySaveRequestDto,
|
||||
instlSncnApplySaveSchema,
|
||||
type InstlSncnMngDetResponse,
|
||||
} from "@/entities/instl-sncn";
|
||||
import { toEsntlAtchFileSaveList } from "@/widgets/pbadms-sprt";
|
||||
|
||||
import type { InstlSncnRegistFormData } from "./instl-sncn-regist-schema";
|
||||
import { parseTaskSeComCdFromApi } from "./instl-sncn-regist-schema";
|
||||
import { formatTaskSeComCdForApi, parseTaskSeComCdFromApi } from "./instl-sncn-regist-schema";
|
||||
|
||||
function mapSelectedValue(value?: string | null): "Y" | "N" | null {
|
||||
if (value === "Y" || value === "N") return value;
|
||||
@@ -16,12 +20,12 @@ export function mapDetailToFormData(data: InstlSncnMngDetResponse): InstlSncnReg
|
||||
return {
|
||||
instlSncnMng: {
|
||||
instlSncnDocNo: detDto.instlSncnDocNo,
|
||||
bizNm: detDto.bizNm,
|
||||
bizBgngYr: detDto.bizBgngYr,
|
||||
bizEndYr: detDto.bizEndYr,
|
||||
bizCn: detDto.bizCn,
|
||||
bizNm: detDto.bizNm ?? "",
|
||||
bizBgngYr: detDto.bizBgngYr ?? "",
|
||||
bizEndYr: detDto.bizEndYr ?? "",
|
||||
bizCn: detDto.bizCn ?? "",
|
||||
taskSeComCd: parseTaskSeComCdFromApi(detDto.taskSeComCd),
|
||||
sncnSeComCd: detDto.sncnSeComCd,
|
||||
sncnSeComCd: detDto.sncnSeComCd ?? "",
|
||||
dsgnrNm: detDto.dsgnrNm ?? "",
|
||||
dsgnrTelno: detDto.dsgnrTelno ?? "",
|
||||
dsgnrOgdpNm: detDto.dsgnrOgdpNm ?? "",
|
||||
@@ -38,16 +42,8 @@ export function mapDetailToFormData(data: InstlSncnMngDetResponse): InstlSncnReg
|
||||
atchFileGroupId: detDto.atchFileGroupId ?? "",
|
||||
rtlztnFcltSeComCd: detDto.rtlztnFcltSeComCd ?? "",
|
||||
rtlztnUsgSeComCd: detDto.rtlztnUsgSeComCd ?? "",
|
||||
envoSeCdNm: detDto.envoSeCdNm,
|
||||
sncnSeCdNm: detDto.sncnSeCdNm,
|
||||
taskSeCdNm: detDto.taskSeCdNm,
|
||||
prgrsSttsCdNm: detDto.prgrsSttsCdNm,
|
||||
sido: detDto.sido,
|
||||
gugun: detDto.gugun,
|
||||
regDt: detDto.regDt,
|
||||
rgtr: detDto.rgtr,
|
||||
rgtrNm: detDto.rgtrNm,
|
||||
mdfcnDt: detDto.mdfcnDt,
|
||||
},
|
||||
bzScaleList: bizSclList.map((item) => ({
|
||||
cnstpSeCd: item.cnstpSeCd,
|
||||
@@ -85,8 +81,6 @@ export function mapDetailToFormData(data: InstlSncnMngDetResponse): InstlSncnReg
|
||||
splmntPicNm: item.splmntPicNm ?? "",
|
||||
splmntCmptnDt: item.splmntCmptnDt ?? "",
|
||||
splmntAtchFileGroupId: item.splmntAtchFileGroupId ?? "",
|
||||
splmntAtchFileNm: item.splmntAtchFileInfo?.orgnlFileNm ?? "",
|
||||
splmntAtchFileAtflId: item.splmntAtchFileInfo?.atflId ?? "",
|
||||
cmptnYn: item.cmptnYn,
|
||||
})) ?? [],
|
||||
privacyConsent: "Y",
|
||||
@@ -132,3 +126,29 @@ export function getCreateDefaultValues(
|
||||
privacyConsent: "N",
|
||||
};
|
||||
}
|
||||
|
||||
export function buildFormPayload(
|
||||
data: InstlSncnRegistFormData,
|
||||
prgrsSttsComCd: "SV" | "ST" | "TE",
|
||||
options: { isEditMode: boolean; docNo?: string },
|
||||
): InstlSncnApplySaveRequestDto {
|
||||
return instlSncnApplySaveSchema.parse({
|
||||
instlSncnMng: {
|
||||
...data.instlSncnMng,
|
||||
instlSncnDocNo: options.isEditMode ? options.docNo : undefined,
|
||||
taskSeComCd: formatTaskSeComCdForApi(data.instlSncnMng.taskSeComCd),
|
||||
prgrsSttsComCd,
|
||||
},
|
||||
bzScaleList: data.bzScaleList,
|
||||
bizCstList: data.bizCstList,
|
||||
cklstResultList: data.cklstResultList.map((item) => ({
|
||||
...item,
|
||||
selected: item.selected ?? undefined,
|
||||
})),
|
||||
esntlAtchFileList: data.esntlAtchFileList,
|
||||
splmntDmndList: data.splmntDmndList.map((item) => ({
|
||||
...item,
|
||||
...(prgrsSttsComCd === "TE" ? { cmptnYn: "Y" } : null),
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
instlSncnBizCstItemSchema,
|
||||
instlSncnBzScaleItemSchema,
|
||||
instlSncnCklstResultItemSchema,
|
||||
instlSncnEsntlAtchFileItemSchema,
|
||||
instlSncnMngSaveSchema,
|
||||
instlSncnSplmntDmndItemSchema,
|
||||
} from "@/entities/instl-sncn";
|
||||
|
||||
export const instlSncnRegistSchema = z.object({
|
||||
instlSncnMng: z.object({
|
||||
instlSncnMng: instlSncnMngSaveSchema.extend({
|
||||
bizNm: z.string().min(1, "사업명은 필수입니다"),
|
||||
bizBgngYr: z.string().min(1, "사업시작연도는 필수입니다"),
|
||||
bizEndYr: z.string().min(1, "사업종료연도는 필수입니다"),
|
||||
@@ -18,32 +27,13 @@ export const instlSncnRegistSchema = z.object({
|
||||
.string()
|
||||
.min(1, "설계자 이메일은 필수입니다")
|
||||
.email("올바른 이메일 형식이 아닙니다"),
|
||||
bgtBizCd: z.string().optional(),
|
||||
bgtAplyYr: z.string().optional(),
|
||||
aplcntNm: z.string().optional(),
|
||||
aplcntMblTelno: z.string().optional(),
|
||||
aplcntOgdpNm: z.string().optional(),
|
||||
aplcntEmlAddr: z.string().optional(),
|
||||
instlSncnDocNo: z.string().nullish(),
|
||||
admdstCd: z.string().optional(),
|
||||
envoSeComCd: z.string().optional(),
|
||||
prgrsSttsComCd: z.string().nullish(),
|
||||
atchFileGroupId: z.string().optional(),
|
||||
rtlztnFcltSeComCd: z.string().optional(),
|
||||
rtlztnUsgSeComCd: z.string().optional(),
|
||||
envoSeCdNm: z.string().nullish(),
|
||||
sncnSeCdNm: z.string().nullish(),
|
||||
taskSeCdNm: z.string().nullish(),
|
||||
prgrsSttsCdNm: z.string().nullish(),
|
||||
sido: z.string().nullish(),
|
||||
gugun: z.string().nullish(),
|
||||
regDt: z.string().nullish(),
|
||||
rgtr: z.string().nullish(),
|
||||
rgtrNm: z.string().nullish(),
|
||||
mdfcnDt: z.string().nullish(),
|
||||
}),
|
||||
bzScaleList: z.array(
|
||||
z.object({
|
||||
instlSncnBzScaleItemSchema.extend({
|
||||
cnstpSeCd: z.string(),
|
||||
bscPl: z.number(),
|
||||
budgetReflct: z.number(),
|
||||
@@ -52,7 +42,7 @@ export const instlSncnRegistSchema = z.object({
|
||||
}),
|
||||
),
|
||||
bizCstList: z.array(
|
||||
z.object({
|
||||
instlSncnBizCstItemSchema.extend({
|
||||
acntgSeCd: z.string(),
|
||||
bscPlanAmt: z.number(),
|
||||
bgtRfltAmt: z.number(),
|
||||
@@ -60,37 +50,25 @@ export const instlSncnRegistSchema = z.object({
|
||||
}),
|
||||
),
|
||||
cklstResultList: z.array(
|
||||
z.object({
|
||||
instlSncnCklstResultItemSchema.extend({
|
||||
cklstSn: z.number(),
|
||||
cklstArtclSn: z.number(),
|
||||
cklstRvwSn: z.number(),
|
||||
rvwStpComCd: z.string(),
|
||||
selected: z.enum(["Y", "N"]).nullable(),
|
||||
selected: z.enum(["Y", "N"]).nullish(),
|
||||
picRvwCn: z.string().nullish(),
|
||||
}),
|
||||
),
|
||||
esntlAtchFileList: z.array(
|
||||
z.object({
|
||||
instlSncnEsntlAtchFileItemSchema.extend({
|
||||
esntlAtchFileId: z.string(),
|
||||
atchFileGroupId: z.string(),
|
||||
}),
|
||||
),
|
||||
splmntDmndList: z.array(
|
||||
z.object({
|
||||
splmntDmndSn: z.number().nullish(),
|
||||
splmntArtclComCd: z.string().nullish(),
|
||||
instlSncnSplmntDmndItemSchema.extend({
|
||||
splmntArtclCdNm: z.string().nullish(),
|
||||
splmntDmndCn: z.string().optional(),
|
||||
splmntDmndDt: z.string().optional(),
|
||||
splmntDmndAtchFileGroupId: z.string().optional(),
|
||||
splmntDmndAtchFileNm: z.string().optional(),
|
||||
splmntCn: z.string().optional(),
|
||||
splmntPicNm: z.string().optional(),
|
||||
splmntCmptnDt: z.string().optional(),
|
||||
splmntAtchFileGroupId: z.string().optional(),
|
||||
splmntAtchFileNm: z.string().optional(),
|
||||
splmntAtchFileAtflId: z.string().optional(),
|
||||
cmptnYn: z.string().optional(),
|
||||
}),
|
||||
),
|
||||
privacyConsent: z.enum(["Y", "N"], {
|
||||
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
InstlSncnSplmntDmndSaveDto,
|
||||
ReviewStepCode,
|
||||
} from "@/entities/instl-sncn";
|
||||
import { instlSncnApplySaveSchema } from "@/entities/instl-sncn";
|
||||
import { type SplmntDmndDialogConfirmPayload, toEsntlAtchFileSaveList } from "@/widgets/pbadms-sprt";
|
||||
|
||||
import type { ChecklistFormState } from "../model/view-types";
|
||||
@@ -23,7 +24,6 @@ export function toBzScaleSaveDto(bizSclList: BizSclTreeDto[]): InstlSncnBzScaleS
|
||||
bscPl: node.bscPlanQty,
|
||||
budgetReflct: node.bgtRfltQty,
|
||||
thtiDsgn: node.tstmDesignQty,
|
||||
diff: node.tstmDesignQty - node.bgtRfltQty,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -46,13 +46,13 @@ export function buildSavePayload(
|
||||
): InstlSncnApplySaveRequestDto {
|
||||
const { detDto, bizSclList, bizCstList, cklstResultList = [], esntlFileList } = data;
|
||||
|
||||
return {
|
||||
return instlSncnApplySaveSchema.parse({
|
||||
instlSncnMng: { ...detDto, prgrsSttsComCd },
|
||||
bzScaleList: toBzScaleSaveDto(bizSclList),
|
||||
bizCstList: toBizCstSaveDto(bizCstList),
|
||||
cklstResultList: toCklstSaveDto(cklstResultList, checklistFormState, reviewStep),
|
||||
esntlAtchFileList: toEsntlAtchFileSaveList(esntlFileList),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function buildTechReviewReplyPayload(
|
||||
@@ -64,12 +64,12 @@ export function buildTechReviewReplyPayload(
|
||||
reviewOpinionFormData?: ReviewOpinionFormData | null,
|
||||
opnrNm?: string,
|
||||
): InstlSncnApplySaveRequestDto {
|
||||
return {
|
||||
return instlSncnApplySaveSchema.parse({
|
||||
...buildSavePayload(data, checklistFormState, reviewStep, prgrsSttsComCd),
|
||||
techReViewSaveCd,
|
||||
rvwStpCd: reviewStep,
|
||||
rvwOpnnList: toRvwOpnnSaveDto(data, reviewOpinionFormData, opnrNm),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function buildSupplementReqPayload(
|
||||
@@ -91,10 +91,10 @@ export function buildSupplementReqPayload(
|
||||
cmptnYn: "N",
|
||||
}));
|
||||
|
||||
return {
|
||||
return instlSncnApplySaveSchema.parse({
|
||||
...buildSavePayload(data, checklistFormState, "LOCAL", "SL"),
|
||||
splmntDmndList: [...existingItems, ...newItems],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function toSplmntDmndSaveItem(item: InstlSncnSplmntDmndSaveDto): InstlSncnSplmntDmndSaveDto {
|
||||
|
||||
@@ -12,7 +12,7 @@ const columnHelper = createColumnHelper<InstlSncnMngResponseDto>();
|
||||
|
||||
const MAIN_STEP_LABELS = {
|
||||
apply: "설치인가 신청",
|
||||
envoReview: "환경청 검토",
|
||||
envoReview: "설치인가 검토",
|
||||
techReview: "기술검토",
|
||||
reply: "검토결과 회신",
|
||||
} as const;
|
||||
@@ -20,7 +20,7 @@ const MAIN_STEP_LABELS = {
|
||||
export const PROGRESS_STATUS_COLUMNS = [
|
||||
{ label: "설치인가 신청 (시군)", sttsComCd: "ST" },
|
||||
{ label: "설치인가 신청 (시도)", sttsComCd: "LE" },
|
||||
{ label: "환경청 검토", sttsComCd: "ER" },
|
||||
{ label: "설치인가 검토", sttsComCd: "ER" },
|
||||
{ label: "기술검토", sttsComCd: "TE" },
|
||||
{ label: "기술검토 회신", sttsComCd: "TP" },
|
||||
{ label: "검토결과 회신", sttsComCd: "EP" },
|
||||
|
||||
@@ -9,10 +9,9 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { SectionCollapsibleGroup, SectionCollapsibleToggle } from "@/shared/ui";
|
||||
import { RhfDevTool, SectionCollapsibleGroup, SectionCollapsibleToggle } from "@/shared/ui";
|
||||
import { useEsntlAtchFileMngTree } from "@/entities/esntl";
|
||||
import {
|
||||
type InstlSncnApplySaveRequestDto,
|
||||
instlSncnKeys,
|
||||
type InstlSncnMngDetResponse,
|
||||
instlSncnService,
|
||||
@@ -31,12 +30,8 @@ import {
|
||||
} from "@/widgets/pbadms-sprt";
|
||||
import { Button } from "@common/components";
|
||||
|
||||
import { getCreateDefaultValues, mapDetailToFormData } from "../lib/form-data-mapper";
|
||||
import {
|
||||
formatTaskSeComCdForApi,
|
||||
type InstlSncnRegistFormData,
|
||||
instlSncnRegistSchema,
|
||||
} from "../lib/instl-sncn-regist-schema";
|
||||
import { buildFormPayload, getCreateDefaultValues, mapDetailToFormData } from "../lib/form-data-mapper";
|
||||
import { type InstlSncnRegistFormData, instlSncnRegistSchema } from "../lib/instl-sncn-regist-schema";
|
||||
import { PROGRESS_STATUS_COLUMNS } from "../model/columns";
|
||||
|
||||
import { BasicInfoFormSection } from "./sections/basic-info-form-section";
|
||||
@@ -72,47 +67,24 @@ function InstlFormPage({ mode, initialData, createDefaults }: InstlFormPageProps
|
||||
: getCreateDefaultValues(userInfo, createDefaults),
|
||||
});
|
||||
|
||||
function buildFormPayload(
|
||||
data: InstlSncnRegistFormData,
|
||||
prgrsSttsComCd: "SV" | "ST" | "TE",
|
||||
): InstlSncnApplySaveRequestDto {
|
||||
const { taskSeComCd: formTaskSeComCd, ...instlSncnMngRest } = data.instlSncnMng;
|
||||
|
||||
return {
|
||||
instlSncnMng: {
|
||||
...instlSncnMngRest,
|
||||
instlSncnDocNo: isEditMode ? initialData?.detDto.instlSncnDocNo : undefined,
|
||||
taskSeComCd: formatTaskSeComCdForApi(formTaskSeComCd),
|
||||
prgrsSttsComCd,
|
||||
},
|
||||
bzScaleList: data.bzScaleList,
|
||||
bizCstList: data.bizCstList,
|
||||
cklstResultList: data.cklstResultList.map((item) => ({
|
||||
...item,
|
||||
selected: item.selected,
|
||||
})),
|
||||
esntlAtchFileList: data.esntlAtchFileList,
|
||||
splmntDmndList: data.splmntDmndList.map(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
({ splmntAtchFileNm, splmntAtchFileAtflId, splmntDmndAtchFileNm, ...item }) => ({
|
||||
...item,
|
||||
...(prgrsSttsComCd === "TE" ? { cmptnYn: "Y" } : null),
|
||||
}),
|
||||
),
|
||||
};
|
||||
function createFormPayload(data: InstlSncnRegistFormData, prgrsSttsComCd: "SV" | "ST" | "TE") {
|
||||
return buildFormPayload(data, prgrsSttsComCd, {
|
||||
isEditMode,
|
||||
docNo: initialData?.detDto.instlSncnDocNo,
|
||||
});
|
||||
}
|
||||
|
||||
async function submitForm(
|
||||
data: InstlSncnRegistFormData,
|
||||
prgrsSttsComCd: "SV" | "ST",
|
||||
): Promise<string | undefined> {
|
||||
return instlSncnService.saveApply(buildFormPayload(data, prgrsSttsComCd));
|
||||
return instlSncnService.saveApply(createFormPayload(data, prgrsSttsComCd));
|
||||
}
|
||||
|
||||
async function submitSupplementForm(data: InstlSncnRegistFormData): Promise<string | undefined> {
|
||||
const docNo = initialData?.detDto.instlSncnDocNo;
|
||||
if (!docNo) return undefined;
|
||||
return instlSncnService.submitSupplement(docNo, buildFormPayload(data, "TE"));
|
||||
return instlSncnService.submitSupplement(docNo, createFormPayload(data, "TE"));
|
||||
}
|
||||
|
||||
async function onSplmntCmptn(data: InstlSncnRegistFormData) {
|
||||
@@ -300,6 +272,7 @@ function InstlFormPage({ mode, initialData, createDefaults }: InstlFormPageProps
|
||||
</div>
|
||||
</SectionCollapsibleGroup>
|
||||
</form>
|
||||
<RhfDevTool control={form.control} />
|
||||
</FormProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,14 +2,11 @@
|
||||
|
||||
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
|
||||
|
||||
import { Paperclip } from "lucide-react";
|
||||
|
||||
import { PROXY_HOST } from "@/shared/config";
|
||||
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 { attachService } from "@/entities/attach";
|
||||
import { AttachmentDownloadLink, attachService } from "@/entities/attach";
|
||||
import type { InstlSncnRvwOpnnResponseDto, ReviewStepCode } from "@/entities/instl-sncn";
|
||||
import {
|
||||
Button,
|
||||
@@ -331,15 +328,10 @@ function TechReviewOpinionTable({ items }: TechReviewOpinionTableProps) {
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
{item.atchFileInfo?.atflCd ? (
|
||||
<a
|
||||
className="inline-flex cursor-pointer items-start gap-1 text-left text-blue-600 hover:underline"
|
||||
href={`${PROXY_HOST}/attach-service/api/v1/atflcd/download/${item.atchFileInfo.atflCd}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<Paperclip className="my-1 size-5 shrink-0" />
|
||||
{item.atchFileInfo.orgnlFileNm || "다운로드"}
|
||||
</a>
|
||||
<AttachmentDownloadLink
|
||||
atflCd={item.atchFileInfo.atflCd}
|
||||
fileName={item.atchFileInfo.orgnlFileNm || "다운로드"}
|
||||
/>
|
||||
) : (
|
||||
"-"
|
||||
)}
|
||||
|
||||
@@ -3,6 +3,7 @@ import type {
|
||||
SewerBscPlanDetResponseDto,
|
||||
SewerBscPlanSplmntDmndItem,
|
||||
} from "@/entities/sewer-bsc-plan";
|
||||
import { sewerBscPlanApplySaveSchema } from "@/entities/sewer-bsc-plan";
|
||||
import { type SplmntDmndDialogConfirmPayload, toEsntlAtchFileSaveList } from "@/widgets/pbadms-sprt";
|
||||
|
||||
import type { ChecklistFormState, ReviewStepCode } from "../model/view-types";
|
||||
@@ -26,7 +27,7 @@ export function buildSavePayload(
|
||||
const cklstResultRows = data.cklstResultRows ?? [];
|
||||
const esntlFileList = data.esntlFileList ?? [];
|
||||
|
||||
return {
|
||||
return sewerBscPlanApplySaveSchema.parse({
|
||||
sewerBscPlan: {
|
||||
sewerBscPlanDocNo: detDto.sewerBscPlanDocNo,
|
||||
admdstCd: detDto.admdstCd,
|
||||
@@ -99,7 +100,7 @@ export function buildSavePayload(
|
||||
})),
|
||||
cklstResultList: toCklstSaveDto(cklstResultRows, checklistFormState, reviewStep),
|
||||
esntlAtchFileList: toEsntlAtchFileSaveList(esntlFileList),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function buildTechReviewReplyPayload(
|
||||
@@ -111,12 +112,12 @@ export function buildTechReviewReplyPayload(
|
||||
reviewOpinionFormData?: ReviewOpinionGetFormDataResult | null,
|
||||
opnrNm?: string,
|
||||
): SewerBscPlanApplySaveRequestDto {
|
||||
return {
|
||||
return sewerBscPlanApplySaveSchema.parse({
|
||||
...buildSavePayload(data, checklistFormState, reviewStep, prgrsSttsComCd),
|
||||
techReViewSaveCd,
|
||||
rvwStpCd: reviewStep,
|
||||
rvwOpnnList: toRvwOpnnSaveDto(data, reviewOpinionFormData, opnrNm),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function buildSupplementReqPayload(
|
||||
@@ -136,10 +137,10 @@ export function buildSupplementReqPayload(
|
||||
cmptnYn: "N",
|
||||
}));
|
||||
|
||||
return {
|
||||
return sewerBscPlanApplySaveSchema.parse({
|
||||
...buildSavePayload(data, checklistFormState, "LOCAL", "SL"),
|
||||
splmntDmndList: [...existingItems, ...newItems],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function toSplmntDmndSaveItem(item: SewerBscPlanSplmntDmndItem): SewerBscPlanSplmntDmndItem {
|
||||
|
||||
@@ -77,7 +77,7 @@ export const sewerBscPlanRegistSchema = z.object({
|
||||
cklstSn: z.number(),
|
||||
cklstArtclSn: z.number(),
|
||||
cklstRvwSn: z.number(),
|
||||
selected: z.enum(["Y", "N"]).nullable(),
|
||||
selected: z.enum(["Y", "N"]).nullish(),
|
||||
picRvwCn: z.string().nullable().optional(),
|
||||
}),
|
||||
),
|
||||
|
||||
@@ -12,14 +12,14 @@ const columnHelper = createColumnHelper<SewerBscPlanResponseDto>();
|
||||
|
||||
const MAIN_STEP_LABELS = {
|
||||
apply: "기본계획 신청",
|
||||
envoReview: "환경청 검토",
|
||||
envoReview: "기본계획 검토",
|
||||
techReview: "기술검토",
|
||||
reply: "검토결과 회신",
|
||||
} as const;
|
||||
|
||||
export const PROGRESS_STATUS_COLUMNS = [
|
||||
{ label: "기본계획 신청 (시군)", sttsComCd: "LE" },
|
||||
{ label: "환경청 검토", sttsComCd: "ER" },
|
||||
{ label: "기본계획 검토", sttsComCd: "ER" },
|
||||
{ label: "기술검토", sttsComCd: "TE" },
|
||||
{ label: "기술검토 회신", sttsComCd: "TP" },
|
||||
{ label: "횐경청 검토 결과 회신", sttsComCd: "EP" },
|
||||
|
||||
@@ -2,14 +2,11 @@
|
||||
|
||||
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
|
||||
|
||||
import { Paperclip } from "lucide-react";
|
||||
|
||||
import { PROXY_HOST } from "@/shared/config";
|
||||
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 { attachService } from "@/entities/attach";
|
||||
import { AttachmentDownloadLink, attachService } from "@/entities/attach";
|
||||
import type { SewerBscPlanRvwOpnnResponseDto } from "@/entities/sewer-bsc-plan";
|
||||
import {
|
||||
Button,
|
||||
@@ -338,15 +335,10 @@ function TechReviewOpinionTable({ items }: TechReviewOpinionTableProps) {
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
{item.atchFileInfo?.atflCd ? (
|
||||
<a
|
||||
className="inline-flex cursor-pointer items-start gap-1 text-left text-blue-600 hover:underline"
|
||||
href={`${PROXY_HOST}/attach-service/api/v1/atflcd/download/${item.atchFileInfo.atflCd}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<Paperclip className="my-1 size-5 shrink-0" />
|
||||
{item.atchFileInfo.orgnlFileNm || "다운로드"}
|
||||
</a>
|
||||
<AttachmentDownloadLink
|
||||
atflCd={item.atchFileInfo.atflCd}
|
||||
fileName={item.atchFileInfo.orgnlFileNm || "다운로드"}
|
||||
/>
|
||||
) : (
|
||||
"-"
|
||||
)}
|
||||
|
||||
@@ -2,10 +2,8 @@
|
||||
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
import { Paperclip } from "lucide-react";
|
||||
|
||||
import { SectionCollapsible } from "@/shared/ui";
|
||||
import { useDownloadAttachmentByAtflCd } from "@/entities/attach";
|
||||
import { AttachmentDownloadLink } from "@/entities/attach";
|
||||
import { useFacilityList } from "@/entities/bns-if";
|
||||
import { useNowDmgMtncGnsvy } from "@/entities/dc";
|
||||
import type {
|
||||
@@ -176,7 +174,6 @@ function TmplntFacilityMatrix({
|
||||
}
|
||||
|
||||
function TmplntSection({ tmplntList, det, dtl }: TmplntSectionProps) {
|
||||
const { mutate: downloadAttachment } = useDownloadAttachmentByAtflCd();
|
||||
const [selectedCode, setSelectedCode] = useState(tmplntList[0]?.swtrmtFcltCd ?? "");
|
||||
const { data: facilityListData } = useFacilityList(
|
||||
{ sido: det.sido ?? "", gugun: det.gugun ?? "" },
|
||||
@@ -261,13 +258,11 @@ function TmplntSection({ tmplntList, det, dtl }: TmplntSectionProps) {
|
||||
<TableCell>{facilityName} 계획평면도</TableCell>
|
||||
<TableCell>
|
||||
{item.atchFileGroupId ? (
|
||||
<a
|
||||
className="inline-flex cursor-pointer items-center gap-1 text-blue-600 hover:underline"
|
||||
onClick={() => downloadAttachment(item.atchFileGroupId ?? "")}
|
||||
>
|
||||
<Paperclip className="size-5 shrink-0" />
|
||||
{item.atchFileInfo?.orgnlFileNm || "다운로드"}
|
||||
</a>
|
||||
<AttachmentDownloadLink
|
||||
atflCd={item.atchFileGroupId}
|
||||
fileName={item.atchFileInfo?.orgnlFileNm || "다운로드"}
|
||||
className="items-center"
|
||||
/>
|
||||
) : (
|
||||
"-"
|
||||
)}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { SectionCollapsibleGroup, SectionCollapsibleToggle } from "@/shared/ui";
|
||||
import { RhfDevTool, SectionCollapsibleGroup, SectionCollapsibleToggle } from "@/shared/ui";
|
||||
import { useAcntgSeMngTree } from "@/entities/acntg";
|
||||
import { useEsntlAtchFileMngTree } from "@/entities/esntl";
|
||||
import {
|
||||
@@ -319,6 +319,7 @@ function SewerBscPlanFormPage({
|
||||
</div>
|
||||
</SectionCollapsibleGroup>
|
||||
</form>
|
||||
<RhfDevTool control={form.control} />
|
||||
</FormProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -26,8 +26,10 @@ export const ENDPOINT = {
|
||||
SEWER_BSC_PLAN: "/as-service/api/v1/sewer-bsc-plans",
|
||||
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",
|
||||
PWRK_CNSLTN_MNG: "/as-service/api/v1/pwrk-cnsltn-mng",
|
||||
PWRK_CNSLTN_DOC_CRT_RSLT: "/as-service/api/v1/pwrk-cnsltn-doc-crt-rslt",
|
||||
PWRK_CNSLTN_DOC_PJTCO_AJMT_MNG: "/as-service/api/v1/pwrk-cnsltn-doc-pjtco-ajmt-mng",
|
||||
},
|
||||
USER_SERVICE: {
|
||||
LOGIN: "/user-service/login",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import type { HTMLAttributes } from "react";
|
||||
|
||||
import type { UseQueryResult } from "@tanstack/react-query";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
import { type Upload } from "tus-js-client";
|
||||
|
||||
@@ -54,9 +54,9 @@ export interface BizCstTreeDto {
|
||||
bscPlanAmt: number;
|
||||
/** 금회설계금액(C) */
|
||||
tstmDesignAmt: number;
|
||||
/** 증감금액(금회설계금액 - 표준사업비, C-B) */
|
||||
/** 증감금액(금회설계금액 - 예산반영금액, C-A) */
|
||||
chgAmt: number;
|
||||
/** 증감율 % ((증감금액 / 표준사업비) * 100) */
|
||||
/** 증감율 % ((증감금액 / 예산반영금액(A)) * 100) */
|
||||
chgRt: number;
|
||||
}
|
||||
|
||||
@@ -151,6 +151,7 @@ export interface ElaprDocPrgrsClientResponseDto {
|
||||
jbgdSeNm?: string;
|
||||
ognzNm?: string;
|
||||
dcarbYn?: string;
|
||||
elaprCycl?: number;
|
||||
}
|
||||
|
||||
/** 문서 상태 변경 이력 응답 DTO */
|
||||
@@ -190,7 +191,7 @@ export interface CklstResultSaveBase {
|
||||
cklstRvwSn?: number;
|
||||
cklstArtclSn?: number;
|
||||
cklstSn?: number;
|
||||
rvwStpComCd: string;
|
||||
rvwStpComCd?: string | null;
|
||||
picRvwCn?: string | null;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { UploadedFileChip } from "./uploaded-file-chip";
|
||||
interface AttachmentFileFieldItem {
|
||||
fileKey: string;
|
||||
fileName: string;
|
||||
onDownload?: () => void;
|
||||
}
|
||||
|
||||
interface AttachmentFileFieldProps {
|
||||
@@ -41,6 +42,7 @@ function AttachmentFileField({
|
||||
fileName={file.fileName}
|
||||
isUploading={isUploadingByKey[file.fileKey] ?? false}
|
||||
onRemove={onRemove ? () => onRemove(index) : undefined}
|
||||
onDownload={file.onDownload}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,7 @@ export type { FileAddButtonProps } from "./file-add-button";
|
||||
export { FileAddButton } from "./file-add-button";
|
||||
export { NoData } from "./no-data";
|
||||
export { PageLayout } from "./page-layout";
|
||||
export { RhfDevTool } from "./rhf-devtool";
|
||||
export { SectionCollapsible } from "./section-collapsible";
|
||||
export {
|
||||
SectionCollapsibleGroup,
|
||||
@@ -12,4 +13,4 @@ export {
|
||||
export type { TextShimmerProps } from "./text-shimmer";
|
||||
export { TEXT_SHIMMER_CLASS, TextShimmer } from "./text-shimmer";
|
||||
export type { UploadedFileChipProps } from "./uploaded-file-chip";
|
||||
export { UploadedFileChip } from "./uploaded-file-chip";
|
||||
export { UploadedFileChip } from "./uploaded-file-chip";
|
||||
@@ -0,0 +1,23 @@
|
||||
"use client";
|
||||
|
||||
import type { Control, FieldValues } from "react-hook-form";
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
const DevTool = dynamic(() => import("@hookform/devtools").then((mod) => mod.DevTool), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
interface RhfDevToolProps<T extends FieldValues> {
|
||||
control: Control<T>;
|
||||
}
|
||||
|
||||
export function RhfDevTool<T extends FieldValues>({ control }: RhfDevToolProps<T>) {
|
||||
if (process.env.NODE_ENV !== "development") return null;
|
||||
return (
|
||||
<DevTool
|
||||
control={control as Control<FieldValues>}
|
||||
placement="bottom-right"
|
||||
styles={{ button: { right: "80px", bottom: "22px", scale: 2.5 } }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -8,15 +8,36 @@ interface UploadedFileChipProps {
|
||||
fileName: string;
|
||||
isUploading?: boolean;
|
||||
onRemove?: () => void;
|
||||
onDownload?: () => void;
|
||||
}
|
||||
|
||||
function UploadedFileChip({ fileName, isUploading = false, onRemove }: UploadedFileChipProps) {
|
||||
function UploadedFileChip({
|
||||
fileName,
|
||||
isUploading = false,
|
||||
onRemove,
|
||||
onDownload,
|
||||
}: UploadedFileChipProps) {
|
||||
const canDownload = Boolean(onDownload) && !isUploading;
|
||||
|
||||
return (
|
||||
<div className="inline-flex items-center justify-between gap-2 rounded-sm border bg-accent p-2">
|
||||
<div className="inline-flex items-center justify-between gap-2 rounded-sm border bg-accent px-3 py-2">
|
||||
<Paperclip className="size-5 shrink-0" />
|
||||
<TextShimmer className="text-xl" active={isUploading}>
|
||||
{fileName}
|
||||
</TextShimmer>
|
||||
{canDownload ? (
|
||||
<button
|
||||
type="button"
|
||||
className="cursor-pointer text-left hover:underline"
|
||||
aria-label={`${fileName} 다운로드`}
|
||||
onClick={onDownload}
|
||||
>
|
||||
<TextShimmer className="text-xl" active={isUploading}>
|
||||
{fileName}
|
||||
</TextShimmer>
|
||||
</button>
|
||||
) : (
|
||||
<TextShimmer className="text-xl" active={isUploading}>
|
||||
{fileName}
|
||||
</TextShimmer>
|
||||
)}
|
||||
{onRemove ? (
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -58,6 +58,7 @@ export interface HwpCtrl {
|
||||
callback?: HwpCtrlSaveAsCallback,
|
||||
callbackUserData?: unknown,
|
||||
) => boolean;
|
||||
ShowHorizontalScroll: (show: boolean) => boolean;
|
||||
}
|
||||
|
||||
export interface HwpCtrlFrameWindow {
|
||||
|
||||
@@ -87,7 +87,10 @@ function HangulGiangi({
|
||||
"HWPX",
|
||||
"",
|
||||
(result) => {
|
||||
const callbackDocumentKey = getCallbackDocumentKey(result.CallbackUserData, documentKey);
|
||||
const callbackDocumentKey = getCallbackDocumentKey(
|
||||
result.CallbackUserData,
|
||||
documentKey,
|
||||
);
|
||||
if (openingDocumentKeyRef.current !== callbackDocumentKey) return;
|
||||
|
||||
openingDocumentKeyRef.current = null;
|
||||
|
||||
@@ -21,14 +21,12 @@ interface CklstResultListItem {
|
||||
cklstSn: number;
|
||||
cklstArtclSn: number;
|
||||
cklstRvwSn: number;
|
||||
rvwStpComCd: string;
|
||||
selected: "Y" | "N" | null;
|
||||
rvwStpComCd?: string | null;
|
||||
selected?: "Y" | "N" | null;
|
||||
picRvwCn?: string | null;
|
||||
}
|
||||
|
||||
export function normalizeTaskSeComCdArray(
|
||||
value: string | string[] | undefined | null,
|
||||
): string[] {
|
||||
export function normalizeTaskSeComCdArray(value: string | string[] | undefined | null): string[] {
|
||||
if (!value) return [];
|
||||
if (Array.isArray(value)) return value.filter(Boolean);
|
||||
return value
|
||||
|
||||
@@ -122,7 +122,7 @@ function findBizCstSyncItem(costItems: CostItemDef[]): CostItemDef | undefined {
|
||||
}
|
||||
|
||||
export interface BizCstTotalAmounts {
|
||||
bscPlanAmt: number;
|
||||
bgtRfltAmt: number;
|
||||
tstmDesignAmt: number;
|
||||
}
|
||||
|
||||
@@ -136,13 +136,13 @@ export function getBizCstTotalRow(
|
||||
costItems: CostItemDef[],
|
||||
): BizCstTotalAmounts {
|
||||
const syncCostItem = findBizCstSyncItem(costItems);
|
||||
if (!syncCostItem) return { bscPlanAmt: 0, tstmDesignAmt: 0 };
|
||||
if (!syncCostItem) return { bgtRfltAmt: 0, tstmDesignAmt: 0 };
|
||||
|
||||
const costRow = bizCstList.find((item) => item.acntgSeCd === syncCostItem.code);
|
||||
if (!costRow) return { bscPlanAmt: 0, tstmDesignAmt: 0 };
|
||||
if (!costRow) return { bgtRfltAmt: 0, tstmDesignAmt: 0 };
|
||||
|
||||
return {
|
||||
bscPlanAmt: costRow.bscPlanAmt || 0,
|
||||
bgtRfltAmt: costRow.bgtRfltAmt || 0,
|
||||
tstmDesignAmt: costRow.tstmDesignAmt || 0,
|
||||
};
|
||||
}
|
||||
@@ -178,7 +178,7 @@ export function calcBizCstSharRemaining(
|
||||
const leafSum = sumFnrsSharLeafAmounts(sharList, sharItems);
|
||||
|
||||
return {
|
||||
exstnRemaining: roundAmount(bizCstTotal.bscPlanAmt - leafSum.exstnAmt),
|
||||
exstnRemaining: roundAmount(bizCstTotal.bgtRfltAmt - leafSum.exstnAmt),
|
||||
chgRqtRemaining: roundAmount(bizCstTotal.tstmDesignAmt - leafSum.chgRqtAmt),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
SectionCollapsible,
|
||||
UploadedFileChip,
|
||||
} from "@/shared/ui";
|
||||
import { attachService, useDownloadAttachmentByAtflCd } from "@/entities/attach";
|
||||
import { attachService, useDownloadAttachmentByAtflCd, useDownloadAttachmentByAtflId } from "@/entities/attach";
|
||||
import { useEsntlAtchFileMngTree } from "@/entities/esntl";
|
||||
import {
|
||||
Button,
|
||||
@@ -217,6 +217,7 @@ function AttachmentsFormSection({
|
||||
{ docSeComCd },
|
||||
);
|
||||
const { mutate: downloadTemplate } = useDownloadAttachmentByAtflCd();
|
||||
const { mutate: downloadByAtflId } = useDownloadAttachmentByAtflId();
|
||||
|
||||
async function invalidateAttachmentQueries() {
|
||||
await queryClient.invalidateQueries({
|
||||
@@ -431,6 +432,9 @@ function AttachmentsFormSection({
|
||||
files={(uploadedFiles[file.esntlAtchFileId] ?? []).map((item) => ({
|
||||
fileKey: item.fileKey,
|
||||
fileName: item.file?.name ?? item.fileName,
|
||||
onDownload: item.atflId
|
||||
? () => downloadByAtflId(item.atflId!)
|
||||
: undefined,
|
||||
}))}
|
||||
inputId={`${file.esntlAtchFileId}-file-input`}
|
||||
isUploadingByKey={isUploading}
|
||||
@@ -470,6 +474,9 @@ function AttachmentsFormSection({
|
||||
fileName={item.file?.name ?? item.fileName}
|
||||
isUploading={isUploading[item.fileKey] ?? false}
|
||||
onRemove={() => void removeEtcFile(idx)}
|
||||
onDownload={
|
||||
item.atflId ? () => downloadByAtflId(item.atflId!) : undefined
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { Paperclip } from "lucide-react";
|
||||
|
||||
import { formatFileSize } from "@/shared/lib/file-utils";
|
||||
import type { AtflMngClientResponseDto, EsntlAtchFileMngTreeResponseDto } from "@/shared/model";
|
||||
import { SectionCollapsible } from "@/shared/ui";
|
||||
import { useDownloadAttachmentByAtflCd, useDownloadAttachmentByAtflId } from "@/entities/attach";
|
||||
import { AttachmentDownloadLink } from "@/entities/attach";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components";
|
||||
|
||||
interface AttachmentsViewSectionProps {
|
||||
@@ -14,9 +12,6 @@ interface AttachmentsViewSectionProps {
|
||||
}
|
||||
|
||||
function AttachmentsViewSection({ esntlFileList, etcFileList }: AttachmentsViewSectionProps) {
|
||||
const { mutate: downloadByAtflCd } = useDownloadAttachmentByAtflCd();
|
||||
const { mutate: downloadByAtflId } = useDownloadAttachmentByAtflId();
|
||||
|
||||
return (
|
||||
<>
|
||||
{esntlFileList.length > 0 && (
|
||||
@@ -48,14 +43,12 @@ function AttachmentsViewSection({ esntlFileList, etcFileList }: AttachmentsViewS
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{files.map((file) => (
|
||||
<a
|
||||
<AttachmentDownloadLink
|
||||
key={file.atflId}
|
||||
className="flex cursor-pointer items-center gap-1 text-blue-600 hover:underline"
|
||||
onClick={() => downloadByAtflId(file.atflId)}
|
||||
>
|
||||
<Paperclip className="size-5 shrink-0 text-blue-600" />
|
||||
{file.orgnlFileNm || "다운로드"}
|
||||
</a>
|
||||
atflId={file.atflId}
|
||||
fileName={file.orgnlFileNm || "다운로드"}
|
||||
className="items-center"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
@@ -95,13 +88,11 @@ function AttachmentsViewSection({ esntlFileList, etcFileList }: AttachmentsViewS
|
||||
<TableRow key={file.atflId}>
|
||||
<TableCell className="text-left">
|
||||
{file.atflCd ? (
|
||||
<a
|
||||
className="flex cursor-pointer items-center gap-1 text-blue-600 hover:underline"
|
||||
onClick={() => downloadByAtflCd(file.atflCd)}
|
||||
>
|
||||
<Paperclip className="size-5 shrink-0 text-blue-600" />
|
||||
{file.orgnlFileNm || file.physFileNm || file.atflId}
|
||||
</a>
|
||||
<AttachmentDownloadLink
|
||||
atflCd={file.atflCd}
|
||||
fileName={file.orgnlFileNm || file.physFileNm || file.atflId}
|
||||
className="items-center"
|
||||
/>
|
||||
) : (
|
||||
<span>{file.orgnlFileNm || file.physFileNm || file.atflId}</span>
|
||||
)}
|
||||
|
||||
@@ -120,14 +120,14 @@ function BusinessCostFormSection() {
|
||||
</TableHead>
|
||||
<TableCell>
|
||||
<Controller
|
||||
name={`bizCstList.${idx}.bscPlanAmt`}
|
||||
name={`bizCstList.${idx}.bgtRfltAmt`}
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<Field data-invalid={fieldState.invalid}>
|
||||
<Input
|
||||
type="number"
|
||||
value={field.value || ""}
|
||||
onChange={(e) => handleCostChange(idx, "bscPlanAmt", e.target.value)}
|
||||
onChange={(e) => handleCostChange(idx, "bgtRfltAmt", e.target.value)}
|
||||
readOnly={item.readonly}
|
||||
aria-invalid={fieldState.invalid}
|
||||
className={`text-right ${item.readonly ? "bg-muted" : ""}`}
|
||||
@@ -138,14 +138,14 @@ function BusinessCostFormSection() {
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Controller
|
||||
name={`bizCstList.${idx}.bgtRfltAmt`}
|
||||
name={`bizCstList.${idx}.bscPlanAmt`}
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<Field data-invalid={fieldState.invalid}>
|
||||
<Input
|
||||
type="number"
|
||||
value={field.value || ""}
|
||||
onChange={(e) => handleCostChange(idx, "bgtRfltAmt", e.target.value)}
|
||||
onChange={(e) => handleCostChange(idx, "bscPlanAmt", e.target.value)}
|
||||
readOnly={item.readonly}
|
||||
aria-invalid={fieldState.invalid}
|
||||
className={`text-right ${item.readonly ? "bg-muted" : ""}`}
|
||||
|
||||
@@ -30,7 +30,7 @@ import {
|
||||
buildChecklistTabs,
|
||||
normalizeTaskSeComCdArray,
|
||||
syncCklstResultListFromTree,
|
||||
} from "../lib/checklist-form-utils";
|
||||
} from "../lib/checklist-form-utils";
|
||||
|
||||
type FieldPrefix = "fnrsCnsltnMng" | "instlSncnMng" | "designCnstnMng" | "sewerBscPlan";
|
||||
|
||||
@@ -39,7 +39,7 @@ interface CklstResultListItem {
|
||||
cklstArtclSn: number;
|
||||
cklstRvwSn: number;
|
||||
rvwStpComCd: string;
|
||||
selected: "Y" | "N" | null;
|
||||
selected?: "Y" | "N" | null;
|
||||
picRvwCn?: string | null;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useEffect, useId, useState } from "react";
|
||||
|
||||
import { getDefaultFileKey, useTus } from "@/shared/lib";
|
||||
import { AttachmentFileField } from "@/shared/ui";
|
||||
import { attachService } from "@/entities/attach";
|
||||
import { attachService, useDownloadAttachmentByAtflId } from "@/entities/attach";
|
||||
import { CodeSelect } from "@/entities/code";
|
||||
import {
|
||||
Button,
|
||||
@@ -96,6 +96,7 @@ function SplmntDmndDialog({
|
||||
}: SplmntDmndDialogProps) {
|
||||
const formId = useId();
|
||||
const [rows, setRows] = useState<SplmntDmndRow[]>([createEmptyRow()]);
|
||||
const { mutate: downloadByAtflId } = useDownloadAttachmentByAtflId();
|
||||
|
||||
const { uploadFileAsync, isUploading, uploads } = useTus({
|
||||
onSuccess: (atflCd, atflId, _file, fileKey) => {
|
||||
@@ -199,7 +200,7 @@ function SplmntDmndDialog({
|
||||
try {
|
||||
await attachService.deleteAttachmentByAtflId(atflId);
|
||||
} catch (error) {
|
||||
console.error("보완요청 첨부파일 삭제 실패:", error);
|
||||
console.error("보완 요청 첨부파일 삭제 실패:", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,7 +227,7 @@ function SplmntDmndDialog({
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-[100rem]" aria-describedby={undefined}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>보완요청</DialogTitle>
|
||||
<DialogTitle>보완 요청</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-3">
|
||||
@@ -289,6 +290,9 @@ function SplmntDmndDialog({
|
||||
files={row.uploadedFiles.map((file) => ({
|
||||
fileKey: file.fileKey,
|
||||
fileName: file.fileName,
|
||||
onDownload: file.atflId
|
||||
? () => downloadByAtflId(file.atflId!)
|
||||
: undefined,
|
||||
}))}
|
||||
inputId={fileInputId}
|
||||
isUploadingByKey={isUploading}
|
||||
@@ -322,7 +326,7 @@ function SplmntDmndDialog({
|
||||
취소
|
||||
</Button>
|
||||
<Button type="button" onClick={handleConfirm} disabled={!canSubmit}>
|
||||
보완요청
|
||||
보완 요청
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user