From d1ccddc1d31d65cf0d6b2bc0bce74e9edab163bb Mon Sep 17 00:00:00 2001 From: bicochan Date: Fri, 11 Sep 2026 17:02:09 +0900 Subject: [PATCH] =?UTF-8?q?[as]=20feat:=20=EC=9E=AC=EC=9B=90=ED=98=91?= =?UTF-8?q?=EC=9D=98=20=EA=B8=B0=EC=88=A0=EA=B2=80=ED=86=A0=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/pwrk-cnsltn-mng-service.ts | 6 + .../entities/pwrk-cnsltn-mng/api/queries.ts | 12 + apps/as/src/entities/pwrk-cnsltn-mng/index.ts | 8 +- .../model/pwrk-cnsltn-mng-interface.ts | 57 +++++ .../design-cnstn/ui/design-form-page.tsx | 4 +- .../pages/fnrs-cnsltn/lib/button-config.ts | 21 +- .../lib/fnrs-cnsltn-regist-schema.ts | 2 +- .../fnrs-cnsltn/lib/save-payload-utils.ts | 21 ++ .../ui/components/floating-action-buttons.tsx | 44 +++- .../components/pwrk-cnsltn-doc-mng-dialog.tsx | 12 +- .../pwrk-cnsltn-rvw-rslt-inq-dialog.tsx | 224 ++++++++++++++++++ .../pages/instl-sncn/ui/instl-form-page.tsx | 4 +- .../ui/sewer-bsc-plan-form-page.tsx | 4 +- apps/as/src/shared/model/pbadms-sprt.ts | 2 +- .../hangulgiangi/ui/hangul-giangi-toolbar.tsx | 12 +- .../widgets/hangulgiangi/ui/hangul-giangi.tsx | 3 + 16 files changed, 415 insertions(+), 21 deletions(-) create mode 100644 apps/as/src/pages/fnrs-cnsltn/ui/components/pwrk-cnsltn-rvw-rslt-inq-dialog.tsx diff --git a/apps/as/src/entities/pwrk-cnsltn-mng/api/pwrk-cnsltn-mng-service.ts b/apps/as/src/entities/pwrk-cnsltn-mng/api/pwrk-cnsltn-mng-service.ts index ac27e8f5d..2654f544f 100644 --- a/apps/as/src/entities/pwrk-cnsltn-mng/api/pwrk-cnsltn-mng-service.ts +++ b/apps/as/src/entities/pwrk-cnsltn-mng/api/pwrk-cnsltn-mng-service.ts @@ -8,11 +8,17 @@ import type { const BASE = ENDPOINT.AS_SERVICE.PWRK_CNSLTN_MNG; +async function findById(pwrkCnsltnDocNo: string): Promise { + const response = await client.get(`${BASE}/${pwrkCnsltnDocNo}`); + return response.data; +} + async function findByUpDocNo(upDocNo: string): Promise { const response = await client.get(`${BASE}/updocno/${upDocNo}`); return response.data; } export const pwrkCnsltnMngService = { + findById, findByUpDocNo, } satisfies PwrkCnsltnMngService; diff --git a/apps/as/src/entities/pwrk-cnsltn-mng/api/queries.ts b/apps/as/src/entities/pwrk-cnsltn-mng/api/queries.ts index a5ba4cd8e..f3dbc0f68 100644 --- a/apps/as/src/entities/pwrk-cnsltn-mng/api/queries.ts +++ b/apps/as/src/entities/pwrk-cnsltn-mng/api/queries.ts @@ -4,9 +4,21 @@ import { pwrkCnsltnMngService } from "./pwrk-cnsltn-mng-service"; export const pwrkCnsltnMngKeys = { all: ["pwrk-cnsltn-mng"] as const, + byId: (pwrkCnsltnDocNo: string) => [...pwrkCnsltnMngKeys.all, "by-id", pwrkCnsltnDocNo] as const, byUpDocNo: (upDocNo: string) => [...pwrkCnsltnMngKeys.all, "by-up-doc-no", upDocNo] as const, }; +export function usePwrkCnsltnMng( + pwrkCnsltnDocNo: string | null | undefined, + isEnabled = true, +) { + return useQuery({ + queryKey: pwrkCnsltnMngKeys.byId(pwrkCnsltnDocNo!), + queryFn: () => pwrkCnsltnMngService.findById(pwrkCnsltnDocNo!), + enabled: Boolean(pwrkCnsltnDocNo) && isEnabled, + }); +} + export function usePwrkCnsltnMngByUpDocNo(upDocNo: string | null | undefined, isEnabled = true) { return useQuery({ queryKey: pwrkCnsltnMngKeys.byUpDocNo(upDocNo!), diff --git a/apps/as/src/entities/pwrk-cnsltn-mng/index.ts b/apps/as/src/entities/pwrk-cnsltn-mng/index.ts index 58a3dc75d..1083b207b 100644 --- a/apps/as/src/entities/pwrk-cnsltn-mng/index.ts +++ b/apps/as/src/entities/pwrk-cnsltn-mng/index.ts @@ -1,9 +1,15 @@ export { pwrkCnsltnMngService } from "./api/pwrk-cnsltn-mng-service"; -export { pwrkCnsltnMngKeys, usePwrkCnsltnMngByUpDocNo } from "./api/queries"; +export { + pwrkCnsltnMngKeys, + usePwrkCnsltnMng, + usePwrkCnsltnMngByUpDocNo, +} from "./api/queries"; export { findCompletedPwrkDraft } from "./lib/find-completed-draft"; export type { + PwrkCnsltnCnsltnResponseDto, PwrkCnsltnDocCrtDmndResponseDto, PwrkCnsltnDocCrtRsltResponseDto, PwrkCnsltnMngDetResponseDto, PwrkCnsltnMngService, + PwrkCnsltnPjtcoAjmtResponseDto, } from "./model/pwrk-cnsltn-mng-interface"; diff --git a/apps/as/src/entities/pwrk-cnsltn-mng/model/pwrk-cnsltn-mng-interface.ts b/apps/as/src/entities/pwrk-cnsltn-mng/model/pwrk-cnsltn-mng-interface.ts index fe5c4e5a4..67065215a 100644 --- a/apps/as/src/entities/pwrk-cnsltn-mng/model/pwrk-cnsltn-mng-interface.ts +++ b/apps/as/src/entities/pwrk-cnsltn-mng/model/pwrk-cnsltn-mng-interface.ts @@ -1,3 +1,5 @@ +import type { AtflMngClientResponseDto } from "@/shared/model"; + export interface PwrkCnsltnDocCrtRsltResponseDto { crtDmndUuid: string; verSn: number; @@ -10,25 +12,80 @@ export interface PwrkCnsltnDocCrtRsltResponseDto { export interface PwrkCnsltnDocCrtDmndResponseDto { crtDmndUuid: string; pwrkCnsltnDocNo: string; + orgnlDocNo?: string; + orgnlDocSeComCd?: string; + orgnlDocSeComCdNm?: string; + crtSysDmndId?: string; crtSttsComCd?: string; crtSttsComCdNm?: string; failRsn?: string; dmndDt?: string; cmptnDt?: string; + rqstrUuid?: string; + rqstrNm?: string; crtRslt?: PwrkCnsltnDocCrtRsltResponseDto | null; } +/** 백엔드 PwrkCnsltnMngDetResponseDto.CnsltnResponseDto */ +export interface PwrkCnsltnCnsltnResponseDto { + pwrkCnsltnDocNo?: string; + rvwOpnnSn?: number; + rvwOpnnClsfComCd?: string; + rvwOpnnClsfComCdNm?: string; + rvwOpnn?: string; + cnsltnCn?: string; + rfltYn?: string; + rmrk?: string; + atchFileGroupId?: string; + atchFileInfo?: AtflMngClientResponseDto; + rvwrNm?: string; + rvwrMblTelno?: string; + rvwrOgdpNm?: string; + rvwrEmlAddr?: string; + lclgvPicNm?: string; + lclgvPicTelno?: string; + lclgvPicOgdpNm?: string; + lclgvPicEmlAddr?: string; + crtDmndUuid?: string; + crtYn?: string; + hwrtChgYn?: string; + verSn?: number; + dtlSn?: number; +} + +/** 백엔드 PwrkCnsltnMngDetResponseDto.PjtcoAjmtResponseDto */ +export interface PwrkCnsltnPjtcoAjmtResponseDto { + pwrkCnsltnDocNo?: string; + rvwOpnnSn?: number; + cnstpSeCd?: string; + rvwOpnnClsfComCd?: string; + rvwOpnnClsfComCdNm?: string; + ognTpcoAmt?: number; + ognGvexpAmt?: number; + ognLcexpAmt?: number; + chgTpcoAmt?: number; + chgGvexpAmt?: number; + chgLcexpAmt?: number; +} + export interface PwrkCnsltnMngDetResponseDto { pwrkCnsltnDocNo: string; upDocNo: string; upDocSeComCd?: string; + upDocSeComCdNm?: string; prgrsSttsComCd?: string; + prgrsSttsComCdNm?: string; bizNm?: string; atchFileGroupId?: string; + regDt?: string; + rgtrNm?: string; + cnsltnList?: PwrkCnsltnCnsltnResponseDto[]; + pjtcoAjmtList?: PwrkCnsltnPjtcoAjmtResponseDto[]; crtDmndList?: PwrkCnsltnDocCrtDmndResponseDto[]; crtRsltList?: PwrkCnsltnDocCrtRsltResponseDto[]; } export interface PwrkCnsltnMngService { + findById(pwrkCnsltnDocNo: string): Promise; findByUpDocNo(upDocNo: string): Promise; } diff --git a/apps/as/src/pages/design-cnstn/ui/design-form-page.tsx b/apps/as/src/pages/design-cnstn/ui/design-form-page.tsx index 4e5a5ad76..041e1d812 100644 --- a/apps/as/src/pages/design-cnstn/ui/design-form-page.tsx +++ b/apps/as/src/pages/design-cnstn/ui/design-form-page.tsx @@ -169,7 +169,9 @@ function DesignFormPage({ mode, initialData, createDefaults }: DesignFormPagePro
- +
+ +
{initialData?.detDto?.designCnstnDocNo && ( ([ - FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwCmptn, + ...ROLE_USER01_ATRZ_STATUS_ACTIONS.TE.ATRZ02, FNRS_CNSLTN_VIEW_BUTTON_ACTION.atrzAprv, FNRS_CNSLTN_VIEW_BUTTON_ACTION.atrzRply, ]); @@ -109,8 +114,14 @@ function resolveRoleUser01AtrzButtonActions( } return actions.filter((action) => { + 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) { + 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.pwrkCnsltnDocCrt) { @@ -118,9 +129,7 @@ function resolveRoleUser01AtrzButtonActions( } } - const needsPendingApproval = PENDING_APPROVAL_ACTIONS.has(action); - - return !needsPendingApproval || options?.hasPendingApprovalRequest !== false; + return true; }); } diff --git a/apps/as/src/pages/fnrs-cnsltn/lib/fnrs-cnsltn-regist-schema.ts b/apps/as/src/pages/fnrs-cnsltn/lib/fnrs-cnsltn-regist-schema.ts index e925ab321..3bc32621a 100644 --- a/apps/as/src/pages/fnrs-cnsltn/lib/fnrs-cnsltn-regist-schema.ts +++ b/apps/as/src/pages/fnrs-cnsltn/lib/fnrs-cnsltn-regist-schema.ts @@ -86,7 +86,7 @@ export const fnrsCnsltnRegistSchema = z.object({ splmntDmndList: z.array( z.object({ splmntDmndSn: z.number().nullish(), - splmntArtclComCd: z.string().optional(), + splmntArtclComCd: z.string().nullable(), splmntArtclCdNm: z.string().nullish(), splmntDmndCn: z.string().optional(), splmntDmndDt: z.string().optional(), diff --git a/apps/as/src/pages/fnrs-cnsltn/lib/save-payload-utils.ts b/apps/as/src/pages/fnrs-cnsltn/lib/save-payload-utils.ts index 6a81db9ee..39ebd7582 100644 --- a/apps/as/src/pages/fnrs-cnsltn/lib/save-payload-utils.ts +++ b/apps/as/src/pages/fnrs-cnsltn/lib/save-payload-utils.ts @@ -10,6 +10,7 @@ import type { FnrsCnsltnMngResponseDto, FnrsCnsltnSplmntDmndItem, } from "@/entities/fnrs-cnsltn"; +import type { PwrkCnsltnCnsltnResponseDto } from "@/entities/pwrk-cnsltn-mng"; import { mapFnrsSharDetToFormList, type SplmntDmndDialogConfirmPayload, @@ -129,6 +130,26 @@ export function buildFileSplmntReqPayload( }; } +export function buildRvwRsltSplmntReqPayload( + data: FnrsCnsltnMngDetResponseDto, + selected: PwrkCnsltnCnsltnResponseDto[], + checklistFormState: ChecklistFormState, +): FnrsCnsltnApplySaveRequestDto { + const existingItems: FnrsCnsltnSplmntDmndItem[] = (data.splmntDmndList ?? []).map((item) => + toSplmntDmndSaveItem(item), + ); + + const newItems: FnrsCnsltnSplmntDmndItem[] = selected.map((row) => ({ + splmntDmndCn: row.rvwOpnn, + cmptnYn: "N", + })); + + return { + ...buildSavePayload(data, checklistFormState, "LOCAL", "SL"), + splmntDmndList: [...existingItems, ...newItems], + }; +} + export function toSplmntDmndSaveItem(item: FnrsCnsltnSplmntDmndItem): FnrsCnsltnSplmntDmndItem { return { splmntDmndSn: item.splmntDmndSn, diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/components/floating-action-buttons.tsx b/apps/as/src/pages/fnrs-cnsltn/ui/components/floating-action-buttons.tsx index e3a6fad4f..f3bb8d088 100644 --- a/apps/as/src/pages/fnrs-cnsltn/ui/components/floating-action-buttons.tsx +++ b/apps/as/src/pages/fnrs-cnsltn/ui/components/floating-action-buttons.tsx @@ -28,7 +28,11 @@ import { fnrsCnsltnMngService, type FnrsExamineRequestDto, } from "@/entities/fnrs-cnsltn"; -import { findCompletedPwrkDraft, usePwrkCnsltnMngByUpDocNo } from "@/entities/pwrk-cnsltn-mng"; +import { + findCompletedPwrkDraft, + type PwrkCnsltnCnsltnResponseDto, + usePwrkCnsltnMngByUpDocNo, +} from "@/entities/pwrk-cnsltn-mng"; import { SplmntDmndDialog, type SplmntDmndDialogConfirmPayload } from "@/widgets/pbadms-sprt"; import { Button } from "@common/components"; @@ -41,6 +45,7 @@ import { import { computeReviewStepChecklistProgress } from "../../lib/checklist-utils"; import { buildFileSplmntReqPayload, + buildRvwRsltSplmntReqPayload, buildSavePayload, buildSupplementReqPayload, buildTechReviewReplyPayload, @@ -51,6 +56,7 @@ import type { TechReviewOpinionSectionHandle } from "../sections/tech-review-opi import { FileVrfcSplmntDialog } from "./file-vrfc-splmnt-dialog"; import { PwrkCnsltnDocMngDialog } from "./pwrk-cnsltn-doc-mng-dialog"; +import { PwrkCnsltnRvwRsltInqDialog } from "./pwrk-cnsltn-rvw-rslt-inq-dialog"; interface FloatingActionButtonsProps { data: FnrsCnsltnMngDetResponseDto; @@ -73,6 +79,7 @@ type ActiveDialog = | "fileSplmnt" | "elaprApproval" | "pwrkCnsltnDocMng" + | "rvwRsltInq" | null; function FloatingActionButtons({ @@ -311,7 +318,7 @@ function FloatingActionButtons({ data, checklistFormState, "ENV", - "EP", + "RM", "CP", getReviewOpinionFormData(), opnrNm, @@ -320,7 +327,7 @@ function FloatingActionButtons({ await executeAction({ action: FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwRply, request: payload, - successMessage: "검토결과 회신이 완료되었습니다.", + successMessage: "재원협의 결과 회신이 완료되었습니다.", }); techReviewOpinionRef?.current?.resetForm(); } @@ -435,6 +442,16 @@ function FloatingActionButtons({ }); } + async function handleRvwRsltSplmntConfirm(selected: PwrkCnsltnCnsltnResponseDto[]) { + setActiveDialog(null); + const requestPayload = buildRvwRsltSplmntReqPayload(data, selected, checklistFormState); + await executeAction({ + action: FNRS_CNSLTN_VIEW_BUTTON_ACTION.dmndSplmnt, + request: requestPayload, + successMessage: "보완요청이 처리되었습니다.", + }); + } + return ( <>
@@ -486,7 +503,7 @@ function FloatingActionButtons({ )} {shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwRply) && ( )} {shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.rqstTechRvw) && ( @@ -518,6 +535,15 @@ function FloatingActionButtons({ 실무협의서 관리 )} + {shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwRsltInq) && ( + + )} {shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwStrg) && ( + + + + + ); +} + +function CnsltnListTable({ + rows, + selectedRowKeys, + onToggleAll, + onToggleRow, +}: CnsltnListTableProps) { + const selectedCount = rows.filter((row, index) => + selectedRowKeys.has(getCnsltnRowKey(row, index)), + ).length; + const isAllSelected = rows.length > 0 && selectedCount === rows.length; + const isSomeSelected = selectedCount > 0 && !isAllSelected; + + return ( + + + + + + + + + + onToggleAll(checked === true)} + aria-label="전체 선택" + /> + + 분류 + 검토의견 + + + + {rows.map((row, index) => { + const rowKey = getCnsltnRowKey(row, index); + const isSelected = selectedRowKeys.has(rowKey); + + return ( + + + onToggleRow(rowKey, checked === true)} + aria-label="행 선택" + /> + + +

{formatOptional(row.rmrk)}

+
+ +

{formatOptional(row.rvwOpnn)}

+
+
+ ); + })} +
+
+ ); +} + +function getCnsltnRowKey(row: PwrkCnsltnCnsltnResponseDto, index: number) { + return [row.pwrkCnsltnDocNo, row.verSn, row.dtlSn, row.rvwOpnnSn, String(index)] + .filter((value) => value !== undefined && value !== null && value !== "") + .join("::"); +} + +function formatOptional(value: string | number | undefined | null) { + if (value === undefined || value === null) return "-"; + const text = String(value).trim(); + return text ? text : "-"; +} + +export { PwrkCnsltnRvwRsltInqDialog }; +export type { PwrkCnsltnRvwRsltInqDialogProps }; diff --git a/apps/as/src/pages/instl-sncn/ui/instl-form-page.tsx b/apps/as/src/pages/instl-sncn/ui/instl-form-page.tsx index adf563c65..a52c9776d 100644 --- a/apps/as/src/pages/instl-sncn/ui/instl-form-page.tsx +++ b/apps/as/src/pages/instl-sncn/ui/instl-form-page.tsx @@ -225,7 +225,9 @@ function InstlFormPage({ mode, initialData, createDefaults }: InstlFormPageProps - +
+ +
{initialData?.detDto.instlSncnDocNo && ( - +
+ +
{initialData?.detDto?.sewerBscPlanDocNo && ( void; onDownload: () => void; onFullscreen: () => void; @@ -13,16 +14,19 @@ interface HangulGiangiToolbarProps { function HangulGiangiToolbar({ isBusy = false, + canSave = true, onSave, onDownload, onFullscreen, }: HangulGiangiToolbarProps) { return (
- + {canSave && ( + + )}