From e18f759fabbcb3fca17c032c0d415873f29f2e48 Mon Sep 17 00:00:00 2001 From: bicochan Date: Tue, 21 Jul 2026 14:32:14 +0900 Subject: [PATCH 1/2] =?UTF-8?q?[as]=20feat:=20=EC=84=A4=EA=B3=84=EC=9E=90?= =?UTF-8?q?=EB=AC=B8=20=EC=97=94=ED=8B=B0=ED=8B=B0=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/design-cnstn-biz-cst-service.ts | 58 +++ .../api/design-cnstn-biz-scl-service.ts | 58 +++ .../design-cnstn-esntl-atch-file-service.ts | 63 +++ .../api/design-cnstn-mng-service.ts | 61 +++ .../api/design-cnstn-rvw-opnn-service.ts | 61 +++ .../src/entities/design-cnstn/api/queries.ts | 411 ++++++++++++++++++ apps/as/src/entities/design-cnstn/index.ts | 88 ++++ .../model/design-cnstn-biz-cst-interface.ts | 54 +++ .../model/design-cnstn-biz-scl-interface.ts | 54 +++ .../design-cnstn-esntl-atch-file-interface.ts | 51 +++ .../model/design-cnstn-mng-interface.ts | 124 ++++++ .../model/design-cnstn-rvw-opnn-interface.ts | 59 +++ apps/as/src/shared/config/api.ts | 5 + pnpm-lock.yaml | 3 + 14 files changed, 1150 insertions(+) create mode 100644 apps/as/src/entities/design-cnstn/api/design-cnstn-biz-cst-service.ts create mode 100644 apps/as/src/entities/design-cnstn/api/design-cnstn-biz-scl-service.ts create mode 100644 apps/as/src/entities/design-cnstn/api/design-cnstn-esntl-atch-file-service.ts create mode 100644 apps/as/src/entities/design-cnstn/api/design-cnstn-mng-service.ts create mode 100644 apps/as/src/entities/design-cnstn/api/design-cnstn-rvw-opnn-service.ts create mode 100644 apps/as/src/entities/design-cnstn/api/queries.ts create mode 100644 apps/as/src/entities/design-cnstn/index.ts create mode 100644 apps/as/src/entities/design-cnstn/model/design-cnstn-biz-cst-interface.ts create mode 100644 apps/as/src/entities/design-cnstn/model/design-cnstn-biz-scl-interface.ts create mode 100644 apps/as/src/entities/design-cnstn/model/design-cnstn-esntl-atch-file-interface.ts create mode 100644 apps/as/src/entities/design-cnstn/model/design-cnstn-mng-interface.ts create mode 100644 apps/as/src/entities/design-cnstn/model/design-cnstn-rvw-opnn-interface.ts diff --git a/apps/as/src/entities/design-cnstn/api/design-cnstn-biz-cst-service.ts b/apps/as/src/entities/design-cnstn/api/design-cnstn-biz-cst-service.ts new file mode 100644 index 000000000..427f6e65a --- /dev/null +++ b/apps/as/src/entities/design-cnstn/api/design-cnstn-biz-cst-service.ts @@ -0,0 +1,58 @@ +import { client } from "@/shared/api"; +import { ENDPOINT } from "@/shared/config"; +import type { PageResponse } from "@/shared/model"; + +import type { + DesignCnstnBizCstId, + DesignCnstnBizCstResponseDto, + DesignCnstnBizCstSaveRequestDto, + DesignCnstnBizCstSearchDto, + DesignCnstnBizCstService, + DesignCnstnBizCstUpdateRequestDto, +} from "../model/design-cnstn-biz-cst-interface"; + +const BASE = ENDPOINT.AS_SERVICE.DESIGN_CNSTN_BIZ_CST; + +async function findPage( + searchDto: DesignCnstnBizCstSearchDto, +): Promise> { + const response = await client.get>(BASE, { + params: searchDto, + }); + return response.data; +} + +async function findById( + designCnstnDocNo: string, + acntgSeCd: string, +): Promise { + const response = await client.get( + `${BASE}/${designCnstnDocNo}/${acntgSeCd}`, + ); + return response.data; +} + +async function save(body: DesignCnstnBizCstSaveRequestDto): Promise { + const response = await client.post(BASE, body); + return response.data; +} + +async function update( + designCnstnDocNo: string, + acntgSeCd: string, + body: DesignCnstnBizCstUpdateRequestDto, +): Promise { + await client.put(`${BASE}/${designCnstnDocNo}/${acntgSeCd}`, body); +} + +async function deleteDesignCnstnBizCst(designCnstnDocNo: string, acntgSeCd: string): Promise { + await client.delete(`${BASE}/${designCnstnDocNo}/${acntgSeCd}`); +} + +export const designCnstnBizCstService = { + findPage, + findById, + save, + update, + delete: deleteDesignCnstnBizCst, +} satisfies DesignCnstnBizCstService; diff --git a/apps/as/src/entities/design-cnstn/api/design-cnstn-biz-scl-service.ts b/apps/as/src/entities/design-cnstn/api/design-cnstn-biz-scl-service.ts new file mode 100644 index 000000000..d4cc8cabf --- /dev/null +++ b/apps/as/src/entities/design-cnstn/api/design-cnstn-biz-scl-service.ts @@ -0,0 +1,58 @@ +import { client } from "@/shared/api"; +import { ENDPOINT } from "@/shared/config"; +import type { PageResponse } from "@/shared/model"; + +import type { + DesignCnstnBizSclId, + DesignCnstnBizSclResponseDto, + DesignCnstnBizSclSaveRequestDto, + DesignCnstnBizSclSearchDto, + DesignCnstnBizSclService, + DesignCnstnBizSclUpdateRequestDto, +} from "../model/design-cnstn-biz-scl-interface"; + +const BASE = ENDPOINT.AS_SERVICE.DESIGN_CNSTN_BIZ_SCL; + +async function findPage( + searchDto: DesignCnstnBizSclSearchDto, +): Promise> { + const response = await client.get>(BASE, { + params: searchDto, + }); + return response.data; +} + +async function findById( + designCnstnDocNo: string, + cnstpSeCd: string, +): Promise { + const response = await client.get( + `${BASE}/${designCnstnDocNo}/${cnstpSeCd}`, + ); + return response.data; +} + +async function save(body: DesignCnstnBizSclSaveRequestDto): Promise { + const response = await client.post(BASE, body); + return response.data; +} + +async function update( + designCnstnDocNo: string, + cnstpSeCd: string, + body: DesignCnstnBizSclUpdateRequestDto, +): Promise { + await client.put(`${BASE}/${designCnstnDocNo}/${cnstpSeCd}`, body); +} + +async function deleteDesignCnstnBizScl(designCnstnDocNo: string, cnstpSeCd: string): Promise { + await client.delete(`${BASE}/${designCnstnDocNo}/${cnstpSeCd}`); +} + +export const designCnstnBizSclService = { + findPage, + findById, + save, + update, + delete: deleteDesignCnstnBizScl, +} satisfies DesignCnstnBizSclService; diff --git a/apps/as/src/entities/design-cnstn/api/design-cnstn-esntl-atch-file-service.ts b/apps/as/src/entities/design-cnstn/api/design-cnstn-esntl-atch-file-service.ts new file mode 100644 index 000000000..a47993149 --- /dev/null +++ b/apps/as/src/entities/design-cnstn/api/design-cnstn-esntl-atch-file-service.ts @@ -0,0 +1,63 @@ +import { client } from "@/shared/api"; +import { ENDPOINT } from "@/shared/config"; +import type { PageResponse } from "@/shared/model"; + +import type { + DesignCnstnEsntlAtchFileId, + DesignCnstnEsntlAtchFileResponseDto, + DesignCnstnEsntlAtchFileSaveRequestDto, + DesignCnstnEsntlAtchFileSearchDto, + DesignCnstnEsntlAtchFileService, + DesignCnstnEsntlAtchFileUpdateRequestDto, +} from "../model/design-cnstn-esntl-atch-file-interface"; + +const BASE = ENDPOINT.AS_SERVICE.DESIGN_CNSTN_ESNTL_ATCH_FILE; + +async function findPage( + searchDto: DesignCnstnEsntlAtchFileSearchDto, +): Promise> { + const response = await client.get>(BASE, { + params: searchDto, + }); + return response.data; +} + +async function findById( + designCnstnDocNo: string, + esntlAtchFileId: string, +): Promise { + const response = await client.get( + `${BASE}/${designCnstnDocNo}/${esntlAtchFileId}`, + ); + return response.data; +} + +async function save( + body: DesignCnstnEsntlAtchFileSaveRequestDto, +): Promise { + const response = await client.post(BASE, body); + return response.data; +} + +async function update( + designCnstnDocNo: string, + esntlAtchFileId: string, + body: DesignCnstnEsntlAtchFileUpdateRequestDto, +): Promise { + await client.put(`${BASE}/${designCnstnDocNo}/${esntlAtchFileId}`, body); +} + +async function deleteDesignCnstnEsntlAtchFile( + designCnstnDocNo: string, + esntlAtchFileId: string, +): Promise { + await client.delete(`${BASE}/${designCnstnDocNo}/${esntlAtchFileId}`); +} + +export const designCnstnEsntlAtchFileService = { + findPage, + findById, + save, + update, + delete: deleteDesignCnstnEsntlAtchFile, +} satisfies DesignCnstnEsntlAtchFileService; diff --git a/apps/as/src/entities/design-cnstn/api/design-cnstn-mng-service.ts b/apps/as/src/entities/design-cnstn/api/design-cnstn-mng-service.ts new file mode 100644 index 000000000..3fc3e52ae --- /dev/null +++ b/apps/as/src/entities/design-cnstn/api/design-cnstn-mng-service.ts @@ -0,0 +1,61 @@ +import { client } from "@/shared/api"; +import { ENDPOINT } from "@/shared/config"; +import type { PageResponse } from "@/shared/model"; + +import type { + DesignCnstnApplySaveRequestDto, + DesignCnstnMngDetResponseDto, + DesignCnstnMngResponseDto, + DesignCnstnMngSaveRequestDto, + DesignCnstnMngSearchDto, + DesignCnstnMngService, + DesignCnstnMngUpdateRequestDto, +} from "../model/design-cnstn-mng-interface"; + +const BASE = ENDPOINT.AS_SERVICE.DESIGN_CNSTN; + +async function findPage( + searchDto: DesignCnstnMngSearchDto, +): Promise> { + const response = await client.get>(BASE, { + params: searchDto, + }); + return response.data; +} + +async function findById(id: string): Promise { + const response = await client.get(`${BASE}/${id}`); + return response.data; +} + +async function save(body: DesignCnstnMngSaveRequestDto): Promise { + const response = await client.post(BASE, body); + return response.data; +} + +async function saveApply(body: DesignCnstnApplySaveRequestDto): Promise { + const response = await client.post(`${BASE}/save-apply`, body); + return response.data; +} + +async function update(id: string, body: DesignCnstnMngUpdateRequestDto): Promise { + await client.put(`${BASE}/${id}`, body); +} + +async function techReview(id: string, body: DesignCnstnApplySaveRequestDto): Promise { + await client.post(`${BASE}/${id}/tech-review`, body); +} + +async function deleteDesignCnstnMng(id: string): Promise { + await client.delete(`${BASE}/${id}`); +} + +export const designCnstnMngService = { + findPage, + findById, + save, + saveApply, + update, + techReview, + delete: deleteDesignCnstnMng, +} satisfies DesignCnstnMngService; diff --git a/apps/as/src/entities/design-cnstn/api/design-cnstn-rvw-opnn-service.ts b/apps/as/src/entities/design-cnstn/api/design-cnstn-rvw-opnn-service.ts new file mode 100644 index 000000000..514a0423f --- /dev/null +++ b/apps/as/src/entities/design-cnstn/api/design-cnstn-rvw-opnn-service.ts @@ -0,0 +1,61 @@ +import { client } from "@/shared/api"; +import { ENDPOINT } from "@/shared/config"; +import type { PageResponse } from "@/shared/model"; + +import type { + DesignCnstnRvwOpnnId, + DesignCnstnRvwOpnnResponseDto, + DesignCnstnRvwOpnnSaveRequestDto, + DesignCnstnRvwOpnnSearchDto, + DesignCnstnRvwOpnnService, + DesignCnstnRvwOpnnUpdateRequestDto, +} from "../model/design-cnstn-rvw-opnn-interface"; + +const BASE = ENDPOINT.AS_SERVICE.DESIGN_CNSTN_RVW_OPNN; + +async function findPage( + searchDto: DesignCnstnRvwOpnnSearchDto, +): Promise> { + const response = await client.get>(BASE, { + params: searchDto, + }); + return response.data; +} + +async function findById( + designCnstnDocNo: string, + rvwOpnnSn: number, +): Promise { + const response = await client.get( + `${BASE}/${designCnstnDocNo}/${rvwOpnnSn}`, + ); + return response.data; +} + +async function save(body: DesignCnstnRvwOpnnSaveRequestDto): Promise { + const response = await client.post(BASE, body); + return response.data; +} + +async function update( + designCnstnDocNo: string, + rvwOpnnSn: number, + body: DesignCnstnRvwOpnnUpdateRequestDto, +): Promise { + await client.put(`${BASE}/${designCnstnDocNo}/${rvwOpnnSn}`, body); +} + +async function deleteDesignCnstnRvwOpnn( + designCnstnDocNo: string, + rvwOpnnSn: number, +): Promise { + await client.delete(`${BASE}/${designCnstnDocNo}/${rvwOpnnSn}`); +} + +export const designCnstnRvwOpnnService = { + findPage, + findById, + save, + update, + delete: deleteDesignCnstnRvwOpnn, +} satisfies DesignCnstnRvwOpnnService; diff --git a/apps/as/src/entities/design-cnstn/api/queries.ts b/apps/as/src/entities/design-cnstn/api/queries.ts new file mode 100644 index 000000000..bafc9784a --- /dev/null +++ b/apps/as/src/entities/design-cnstn/api/queries.ts @@ -0,0 +1,411 @@ +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; + +import { ENDPOINT } from "@/shared/config"; + +import type { + DesignCnstnBizCstSaveRequestDto, + DesignCnstnBizCstSearchDto, + DesignCnstnBizCstUpdateRequestDto, +} from "../model/design-cnstn-biz-cst-interface"; +import type { + DesignCnstnBizSclSaveRequestDto, + DesignCnstnBizSclSearchDto, + DesignCnstnBizSclUpdateRequestDto, +} from "../model/design-cnstn-biz-scl-interface"; +import type { + DesignCnstnEsntlAtchFileSaveRequestDto, + DesignCnstnEsntlAtchFileSearchDto, + DesignCnstnEsntlAtchFileUpdateRequestDto, +} from "../model/design-cnstn-esntl-atch-file-interface"; +import type { + DesignCnstnApplySaveRequestDto, + DesignCnstnMngSaveRequestDto, + DesignCnstnMngSearchDto, + DesignCnstnMngUpdateRequestDto, +} from "../model/design-cnstn-mng-interface"; +import type { + DesignCnstnRvwOpnnSaveRequestDto, + DesignCnstnRvwOpnnSearchDto, + DesignCnstnRvwOpnnUpdateRequestDto, +} from "../model/design-cnstn-rvw-opnn-interface"; + +import { designCnstnBizCstService } from "./design-cnstn-biz-cst-service"; +import { designCnstnBizSclService } from "./design-cnstn-biz-scl-service"; +import { designCnstnEsntlAtchFileService } from "./design-cnstn-esntl-atch-file-service"; +import { designCnstnMngService } from "./design-cnstn-mng-service"; +import { designCnstnRvwOpnnService } from "./design-cnstn-rvw-opnn-service"; + +export const designCnstnMngBaseKey = [ENDPOINT.AS_SERVICE.DESIGN_CNSTN] as const; +export const designCnstnBizCstBaseKey = [ENDPOINT.AS_SERVICE.DESIGN_CNSTN_BIZ_CST] as const; +export const designCnstnBizSclBaseKey = [ENDPOINT.AS_SERVICE.DESIGN_CNSTN_BIZ_SCL] as const; +export const designCnstnEsntlAtchFileBaseKey = [ + ENDPOINT.AS_SERVICE.DESIGN_CNSTN_ESNTL_ATCH_FILE, +] as const; +export const designCnstnRvwOpnnBaseKey = [ENDPOINT.AS_SERVICE.DESIGN_CNSTN_RVW_OPNN] as const; + +/* ─── DesignCnstnMng ─── */ + +export function useDesignCnstnMngPage(params: DesignCnstnMngSearchDto) { + return useQuery({ + queryKey: [...designCnstnMngBaseKey, "findPage", params], + queryFn: () => designCnstnMngService.findPage(params), + }); +} + +export function useDesignCnstnMng(id: string | null | undefined) { + return useQuery({ + queryKey: [...designCnstnMngBaseKey, "findById", id], + queryFn: () => designCnstnMngService.findById(id!), + enabled: Boolean(id), + }); +} + +export function useSaveDesignCnstnMng() { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (body: DesignCnstnMngSaveRequestDto) => designCnstnMngService.save(body), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: designCnstnMngBaseKey }); + }, + }); +} + +export function useSaveApplyDesignCnstnMng() { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (body: DesignCnstnApplySaveRequestDto) => designCnstnMngService.saveApply(body), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: designCnstnMngBaseKey }); + }, + }); +} + +export function useUpdateDesignCnstnMng() { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: ({ id, body }: { id: string; body: DesignCnstnMngUpdateRequestDto }) => + designCnstnMngService.update(id, body), + onSuccess: (_data, { id }) => { + queryClient.invalidateQueries({ queryKey: designCnstnMngBaseKey }); + queryClient.invalidateQueries({ + queryKey: [...designCnstnMngBaseKey, "findById", id], + }); + }, + }); +} + +export function useTechReviewDesignCnstnMng() { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: ({ id, body }: { id: string; body: DesignCnstnApplySaveRequestDto }) => + designCnstnMngService.techReview(id, body), + onSuccess: (_data, { id }) => { + queryClient.invalidateQueries({ queryKey: designCnstnMngBaseKey }); + queryClient.invalidateQueries({ + queryKey: [...designCnstnMngBaseKey, "findById", id], + }); + }, + }); +} + +export function useDeleteDesignCnstnMng() { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (id: string) => designCnstnMngService.delete(id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: designCnstnMngBaseKey }); + }, + }); +} + +/* ─── DesignCnstnBizCst ─── */ + +export function useDesignCnstnBizCstPage(params: DesignCnstnBizCstSearchDto) { + return useQuery({ + queryKey: [...designCnstnBizCstBaseKey, "findPage", params], + queryFn: () => designCnstnBizCstService.findPage(params), + }); +} + +export function useDesignCnstnBizCst( + designCnstnDocNo: string | null | undefined, + acntgSeCd: string | null | undefined, +) { + return useQuery({ + queryKey: [...designCnstnBizCstBaseKey, "findById", designCnstnDocNo, acntgSeCd], + queryFn: () => designCnstnBizCstService.findById(designCnstnDocNo!, acntgSeCd!), + enabled: Boolean(designCnstnDocNo && acntgSeCd), + }); +} + +export function useSaveDesignCnstnBizCst() { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (body: DesignCnstnBizCstSaveRequestDto) => designCnstnBizCstService.save(body), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: designCnstnBizCstBaseKey }); + queryClient.invalidateQueries({ queryKey: designCnstnMngBaseKey }); + }, + }); +} + +export function useUpdateDesignCnstnBizCst() { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: ({ + designCnstnDocNo, + acntgSeCd, + body, + }: { + designCnstnDocNo: string; + acntgSeCd: string; + body: DesignCnstnBizCstUpdateRequestDto; + }) => designCnstnBizCstService.update(designCnstnDocNo, acntgSeCd, body), + onSuccess: (_data, { designCnstnDocNo, acntgSeCd }) => { + queryClient.invalidateQueries({ queryKey: designCnstnBizCstBaseKey }); + queryClient.invalidateQueries({ + queryKey: [...designCnstnBizCstBaseKey, "findById", designCnstnDocNo, acntgSeCd], + }); + queryClient.invalidateQueries({ queryKey: designCnstnMngBaseKey }); + }, + }); +} + +export function useDeleteDesignCnstnBizCst() { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: ({ + designCnstnDocNo, + acntgSeCd, + }: { + designCnstnDocNo: string; + acntgSeCd: string; + }) => designCnstnBizCstService.delete(designCnstnDocNo, acntgSeCd), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: designCnstnBizCstBaseKey }); + queryClient.invalidateQueries({ queryKey: designCnstnMngBaseKey }); + }, + }); +} + +/* ─── DesignCnstnBizScl ─── */ + +export function useDesignCnstnBizSclPage(params: DesignCnstnBizSclSearchDto) { + return useQuery({ + queryKey: [...designCnstnBizSclBaseKey, "findPage", params], + queryFn: () => designCnstnBizSclService.findPage(params), + }); +} + +export function useDesignCnstnBizScl( + designCnstnDocNo: string | null | undefined, + cnstpSeCd: string | null | undefined, +) { + return useQuery({ + queryKey: [...designCnstnBizSclBaseKey, "findById", designCnstnDocNo, cnstpSeCd], + queryFn: () => designCnstnBizSclService.findById(designCnstnDocNo!, cnstpSeCd!), + enabled: Boolean(designCnstnDocNo && cnstpSeCd), + }); +} + +export function useSaveDesignCnstnBizScl() { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (body: DesignCnstnBizSclSaveRequestDto) => designCnstnBizSclService.save(body), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: designCnstnBizSclBaseKey }); + queryClient.invalidateQueries({ queryKey: designCnstnMngBaseKey }); + }, + }); +} + +export function useUpdateDesignCnstnBizScl() { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: ({ + designCnstnDocNo, + cnstpSeCd, + body, + }: { + designCnstnDocNo: string; + cnstpSeCd: string; + body: DesignCnstnBizSclUpdateRequestDto; + }) => designCnstnBizSclService.update(designCnstnDocNo, cnstpSeCd, body), + onSuccess: (_data, { designCnstnDocNo, cnstpSeCd }) => { + queryClient.invalidateQueries({ queryKey: designCnstnBizSclBaseKey }); + queryClient.invalidateQueries({ + queryKey: [...designCnstnBizSclBaseKey, "findById", designCnstnDocNo, cnstpSeCd], + }); + queryClient.invalidateQueries({ queryKey: designCnstnMngBaseKey }); + }, + }); +} + +export function useDeleteDesignCnstnBizScl() { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: ({ + designCnstnDocNo, + cnstpSeCd, + }: { + designCnstnDocNo: string; + cnstpSeCd: string; + }) => designCnstnBizSclService.delete(designCnstnDocNo, cnstpSeCd), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: designCnstnBizSclBaseKey }); + queryClient.invalidateQueries({ queryKey: designCnstnMngBaseKey }); + }, + }); +} + +/* ─── DesignCnstnEsntlAtchFile ─── */ + +export function useDesignCnstnEsntlAtchFilePage(params: DesignCnstnEsntlAtchFileSearchDto) { + return useQuery({ + queryKey: [...designCnstnEsntlAtchFileBaseKey, "findPage", params], + queryFn: () => designCnstnEsntlAtchFileService.findPage(params), + }); +} + +export function useDesignCnstnEsntlAtchFile( + designCnstnDocNo: string | null | undefined, + esntlAtchFileId: string | null | undefined, +) { + return useQuery({ + queryKey: [ + ...designCnstnEsntlAtchFileBaseKey, + "findById", + designCnstnDocNo, + esntlAtchFileId, + ], + queryFn: () => + designCnstnEsntlAtchFileService.findById(designCnstnDocNo!, esntlAtchFileId!), + enabled: Boolean(designCnstnDocNo && esntlAtchFileId), + }); +} + +export function useSaveDesignCnstnEsntlAtchFile() { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (body: DesignCnstnEsntlAtchFileSaveRequestDto) => + designCnstnEsntlAtchFileService.save(body), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: designCnstnEsntlAtchFileBaseKey }); + queryClient.invalidateQueries({ queryKey: designCnstnMngBaseKey }); + }, + }); +} + +export function useUpdateDesignCnstnEsntlAtchFile() { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: ({ + designCnstnDocNo, + esntlAtchFileId, + body, + }: { + designCnstnDocNo: string; + esntlAtchFileId: string; + body: DesignCnstnEsntlAtchFileUpdateRequestDto; + }) => designCnstnEsntlAtchFileService.update(designCnstnDocNo, esntlAtchFileId, body), + onSuccess: (_data, { designCnstnDocNo, esntlAtchFileId }) => { + queryClient.invalidateQueries({ queryKey: designCnstnEsntlAtchFileBaseKey }); + queryClient.invalidateQueries({ + queryKey: [ + ...designCnstnEsntlAtchFileBaseKey, + "findById", + designCnstnDocNo, + esntlAtchFileId, + ], + }); + queryClient.invalidateQueries({ queryKey: designCnstnMngBaseKey }); + }, + }); +} + +export function useDeleteDesignCnstnEsntlAtchFile() { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: ({ + designCnstnDocNo, + esntlAtchFileId, + }: { + designCnstnDocNo: string; + esntlAtchFileId: string; + }) => designCnstnEsntlAtchFileService.delete(designCnstnDocNo, esntlAtchFileId), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: designCnstnEsntlAtchFileBaseKey }); + queryClient.invalidateQueries({ queryKey: designCnstnMngBaseKey }); + }, + }); +} + +/* ─── DesignCnstnRvwOpnn ─── */ + +export function useDesignCnstnRvwOpnnPage(params: DesignCnstnRvwOpnnSearchDto) { + return useQuery({ + queryKey: [...designCnstnRvwOpnnBaseKey, "findPage", params], + queryFn: () => designCnstnRvwOpnnService.findPage(params), + }); +} + +export function useDesignCnstnRvwOpnn( + designCnstnDocNo: string | null | undefined, + rvwOpnnSn: number | null | undefined, +) { + return useQuery({ + queryKey: [...designCnstnRvwOpnnBaseKey, "findById", designCnstnDocNo, rvwOpnnSn], + queryFn: () => designCnstnRvwOpnnService.findById(designCnstnDocNo!, rvwOpnnSn!), + enabled: Boolean(designCnstnDocNo && rvwOpnnSn != null), + }); +} + +export function useSaveDesignCnstnRvwOpnn() { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (body: DesignCnstnRvwOpnnSaveRequestDto) => designCnstnRvwOpnnService.save(body), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: designCnstnRvwOpnnBaseKey }); + queryClient.invalidateQueries({ queryKey: designCnstnMngBaseKey }); + }, + }); +} + +export function useUpdateDesignCnstnRvwOpnn() { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: ({ + designCnstnDocNo, + rvwOpnnSn, + body, + }: { + designCnstnDocNo: string; + rvwOpnnSn: number; + body: DesignCnstnRvwOpnnUpdateRequestDto; + }) => designCnstnRvwOpnnService.update(designCnstnDocNo, rvwOpnnSn, body), + onSuccess: (_data, { designCnstnDocNo, rvwOpnnSn }) => { + queryClient.invalidateQueries({ queryKey: designCnstnRvwOpnnBaseKey }); + queryClient.invalidateQueries({ + queryKey: [...designCnstnRvwOpnnBaseKey, "findById", designCnstnDocNo, rvwOpnnSn], + }); + queryClient.invalidateQueries({ queryKey: designCnstnMngBaseKey }); + }, + }); +} + +export function useDeleteDesignCnstnRvwOpnn() { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: ({ + designCnstnDocNo, + rvwOpnnSn, + }: { + designCnstnDocNo: string; + rvwOpnnSn: number; + }) => designCnstnRvwOpnnService.delete(designCnstnDocNo, rvwOpnnSn), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: designCnstnRvwOpnnBaseKey }); + queryClient.invalidateQueries({ queryKey: designCnstnMngBaseKey }); + }, + }); +} diff --git a/apps/as/src/entities/design-cnstn/index.ts b/apps/as/src/entities/design-cnstn/index.ts new file mode 100644 index 000000000..ed6b6e1ae --- /dev/null +++ b/apps/as/src/entities/design-cnstn/index.ts @@ -0,0 +1,88 @@ +export { designCnstnBizCstService } from "./api/design-cnstn-biz-cst-service"; +export { designCnstnBizSclService } from "./api/design-cnstn-biz-scl-service"; +export { designCnstnEsntlAtchFileService } from "./api/design-cnstn-esntl-atch-file-service"; +export { designCnstnMngService } from "./api/design-cnstn-mng-service"; +export { designCnstnRvwOpnnService } from "./api/design-cnstn-rvw-opnn-service"; +export { + designCnstnBizCstBaseKey, + designCnstnBizSclBaseKey, + designCnstnEsntlAtchFileBaseKey, + designCnstnMngBaseKey, + designCnstnRvwOpnnBaseKey, + useDeleteDesignCnstnBizCst, + useDeleteDesignCnstnBizScl, + useDeleteDesignCnstnEsntlAtchFile, + useDeleteDesignCnstnMng, + useDeleteDesignCnstnRvwOpnn, + useDesignCnstnBizCst, + useDesignCnstnBizCstPage, + useDesignCnstnBizScl, + useDesignCnstnBizSclPage, + useDesignCnstnEsntlAtchFile, + useDesignCnstnEsntlAtchFilePage, + useDesignCnstnMng, + useDesignCnstnMngPage, + useDesignCnstnRvwOpnn, + useDesignCnstnRvwOpnnPage, + useSaveApplyDesignCnstnMng, + useSaveDesignCnstnBizCst, + useSaveDesignCnstnBizScl, + useSaveDesignCnstnEsntlAtchFile, + useSaveDesignCnstnMng, + useSaveDesignCnstnRvwOpnn, + useTechReviewDesignCnstnMng, + useUpdateDesignCnstnBizCst, + useUpdateDesignCnstnBizScl, + useUpdateDesignCnstnEsntlAtchFile, + useUpdateDesignCnstnMng, + useUpdateDesignCnstnRvwOpnn, +} from "./api/queries"; +export type { + DesignCnstnBizCstId, + DesignCnstnBizCstResponseDto, + DesignCnstnBizCstSaveRequestDto, + DesignCnstnBizCstSearchDto, + DesignCnstnBizCstService, + DesignCnstnBizCstUpdateRequestDto, +} from "./model/design-cnstn-biz-cst-interface"; +export type { + DesignCnstnBizSclId, + DesignCnstnBizSclResponseDto, + DesignCnstnBizSclSaveRequestDto, + DesignCnstnBizSclSearchDto, + DesignCnstnBizSclService, + DesignCnstnBizSclUpdateRequestDto, +} from "./model/design-cnstn-biz-scl-interface"; +export type { + DesignCnstnEsntlAtchFileId, + DesignCnstnEsntlAtchFileResponseDto, + DesignCnstnEsntlAtchFileSaveRequestDto, + DesignCnstnEsntlAtchFileSearchDto, + DesignCnstnEsntlAtchFileService, + DesignCnstnEsntlAtchFileUpdateRequestDto, +} 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 { + DesignCnstnRvwOpnnId, + DesignCnstnRvwOpnnSearchDto, + DesignCnstnRvwOpnnService, + DesignCnstnRvwOpnnUpdateRequestDto, +} from "./model/design-cnstn-rvw-opnn-interface"; diff --git a/apps/as/src/entities/design-cnstn/model/design-cnstn-biz-cst-interface.ts b/apps/as/src/entities/design-cnstn/model/design-cnstn-biz-cst-interface.ts new file mode 100644 index 000000000..bbf6519e9 --- /dev/null +++ b/apps/as/src/entities/design-cnstn/model/design-cnstn-biz-cst-interface.ts @@ -0,0 +1,54 @@ +import type { PageableParams, PageResponse } from "@/shared/model"; + +/** 백엔드 DesignCnstnBizCstId */ +export interface DesignCnstnBizCstId { + designCnstnMng: string; + acntgSeCd: string; +} + +/** 백엔드 DesignCnstnBizCstSaveRequestDto */ +export interface DesignCnstnBizCstSaveRequestDto { + designCnstnDocNo: string; + acntgSeCd: string; + bscPlanAmt?: number; + bgtRfltAmt?: number; + tstmDesignAmt?: number; +} + +/** 백엔드 DesignCnstnBizCstUpdateRequestDto */ +export interface DesignCnstnBizCstUpdateRequestDto { + bscPlanAmt?: number; + bgtRfltAmt?: number; + tstmDesignAmt?: number; +} + +/** 백엔드 DesignCnstnBizCstSearchDto + Pageable */ +export interface DesignCnstnBizCstSearchDto extends PageableParams { + designCnstnDocNo?: string; + acntgSeCd?: string; +} + +/** 백엔드 DesignCnstnBizCstResponseDto */ +export interface DesignCnstnBizCstResponseDto { + designCnstnDocNo: string; + acntgSeCd: string; + bscPlanAmt?: number; + bgtRfltAmt?: number; + tstmDesignAmt?: number; + regDt?: string; +} + +/** Controller public 메서드명·시그니처와 동일 */ +export interface DesignCnstnBizCstService { + findPage( + searchDto: DesignCnstnBizCstSearchDto, + ): Promise>; + findById(designCnstnDocNo: string, acntgSeCd: string): Promise; + save(body: DesignCnstnBizCstSaveRequestDto): Promise; + update( + designCnstnDocNo: string, + acntgSeCd: string, + body: DesignCnstnBizCstUpdateRequestDto, + ): Promise; + delete(designCnstnDocNo: string, acntgSeCd: string): Promise; +} diff --git a/apps/as/src/entities/design-cnstn/model/design-cnstn-biz-scl-interface.ts b/apps/as/src/entities/design-cnstn/model/design-cnstn-biz-scl-interface.ts new file mode 100644 index 000000000..b7fd92140 --- /dev/null +++ b/apps/as/src/entities/design-cnstn/model/design-cnstn-biz-scl-interface.ts @@ -0,0 +1,54 @@ +import type { PageableParams, PageResponse } from "@/shared/model"; + +/** 백엔드 DesignCnstnBizSclId */ +export interface DesignCnstnBizSclId { + designCnstnMng: string; + cnstpSeCd: string; +} + +/** 백엔드 DesignCnstnBizSclSaveRequestDto */ +export interface DesignCnstnBizSclSaveRequestDto { + designCnstnDocNo: string; + cnstpSeCd: string; + bscPlanQty?: number; + bgtRfltQty?: number; + tstmDesignQty?: number; +} + +/** 백엔드 DesignCnstnBizSclUpdateRequestDto */ +export interface DesignCnstnBizSclUpdateRequestDto { + bscPlanQty?: number; + bgtRfltQty?: number; + tstmDesignQty?: number; +} + +/** 백엔드 DesignCnstnBizSclSearchDto + Pageable */ +export interface DesignCnstnBizSclSearchDto extends PageableParams { + designCnstnDocNo?: string; + cnstpSeCd?: string; +} + +/** 백엔드 DesignCnstnBizSclResponseDto */ +export interface DesignCnstnBizSclResponseDto { + designCnstnDocNo: string; + cnstpSeCd: string; + bscPlanQty?: number; + bgtRfltQty?: number; + tstmDesignQty?: number; + regDt?: string; +} + +/** Controller public 메서드명·시그니처와 동일 */ +export interface DesignCnstnBizSclService { + findPage( + searchDto: DesignCnstnBizSclSearchDto, + ): Promise>; + findById(designCnstnDocNo: string, cnstpSeCd: string): Promise; + save(body: DesignCnstnBizSclSaveRequestDto): Promise; + update( + designCnstnDocNo: string, + cnstpSeCd: string, + body: DesignCnstnBizSclUpdateRequestDto, + ): Promise; + delete(designCnstnDocNo: string, cnstpSeCd: string): Promise; +} diff --git a/apps/as/src/entities/design-cnstn/model/design-cnstn-esntl-atch-file-interface.ts b/apps/as/src/entities/design-cnstn/model/design-cnstn-esntl-atch-file-interface.ts new file mode 100644 index 000000000..82666b60a --- /dev/null +++ b/apps/as/src/entities/design-cnstn/model/design-cnstn-esntl-atch-file-interface.ts @@ -0,0 +1,51 @@ +import type { PageableParams, PageResponse } from "@/shared/model"; + +/** 백엔드 DesignCnstnEsntlAtchFileId */ +export interface DesignCnstnEsntlAtchFileId { + designCnstnMng: string; + esntlAtchFileId: string; +} + +/** 백엔드 DesignCnstnEsntlAtchFileSaveRequestDto */ +export interface DesignCnstnEsntlAtchFileSaveRequestDto { + designCnstnDocNo: string; + esntlAtchFileId: string; + atchFileGroupId?: string; +} + +/** 백엔드 DesignCnstnEsntlAtchFileUpdateRequestDto */ +export interface DesignCnstnEsntlAtchFileUpdateRequestDto { + atchFileGroupId?: string; +} + +/** 백엔드 DesignCnstnEsntlAtchFileSearchDto + Pageable */ +export interface DesignCnstnEsntlAtchFileSearchDto extends PageableParams { + designCnstnDocNo?: string; + esntlAtchFileId?: string; +} + +/** 백엔드 DesignCnstnEsntlAtchFileResponseDto */ +export interface DesignCnstnEsntlAtchFileResponseDto { + designCnstnDocNo: string; + esntlAtchFileId: string; + atchFileGroupId?: string; + regDt?: string; +} + +/** Controller public 메서드명·시그니처와 동일 */ +export interface DesignCnstnEsntlAtchFileService { + findPage( + searchDto: DesignCnstnEsntlAtchFileSearchDto, + ): Promise>; + findById( + designCnstnDocNo: string, + esntlAtchFileId: string, + ): Promise; + save(body: DesignCnstnEsntlAtchFileSaveRequestDto): Promise; + update( + designCnstnDocNo: string, + esntlAtchFileId: string, + body: DesignCnstnEsntlAtchFileUpdateRequestDto, + ): Promise; + delete(designCnstnDocNo: string, esntlAtchFileId: string): Promise; +} diff --git a/apps/as/src/entities/design-cnstn/model/design-cnstn-mng-interface.ts b/apps/as/src/entities/design-cnstn/model/design-cnstn-mng-interface.ts new file mode 100644 index 000000000..1ee2be991 --- /dev/null +++ b/apps/as/src/entities/design-cnstn/model/design-cnstn-mng-interface.ts @@ -0,0 +1,124 @@ +import type { + BizCstSaveBase, + BzScaleSaveBase, + EsntlAtchFileSaveBase, + PageableParams, + PageResponse, + PbadmsApplySaveHeader, + PbadmsMngDetResponseBase, +} from "@/shared/model"; + +import type { + DesignCnstnRvwOpnnResponseDto, + DesignCnstnRvwOpnnSaveRequestDto, +} from "./design-cnstn-rvw-opnn-interface"; + +export type { + DesignCnstnRvwOpnnResponseDto, + DesignCnstnRvwOpnnSaveRequestDto, +} from "./design-cnstn-rvw-opnn-interface"; +export type { + AtflMngClientResponseDto, + BizCstTreeDto, + BizSclTreeDto, + ElaprDocPrgrsClientResponseDto, + 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; + +/** 백엔드 DesignCnstnMngSearchDto + Pageable */ +export interface DesignCnstnMngSearchDto extends PageableParams { + 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; +} + +interface DesignCnstnMngResponseExtension { + designCnstnDocNo: string; + envoSeCdNm?: string; + taskSeCdNm?: string; + prgrsSttsCdNm?: string; + regDt?: string; + rgtr?: string; + rgtrNm?: string; + mdfcnDt?: string; + sido?: string; + gugun?: string; +} + +/** 백엔드 DesignCnstnMngResponseDto */ +export type DesignCnstnMngResponseDto = Omit & + 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>; + findById(id: string): Promise; + save(body: DesignCnstnMngSaveRequestDto): Promise; + saveApply(body: DesignCnstnApplySaveRequestDto): Promise; + update(id: string, body: DesignCnstnMngUpdateRequestDto): Promise; + techReview(id: string, body: DesignCnstnApplySaveRequestDto): Promise; + delete(id: string): Promise; +} diff --git a/apps/as/src/entities/design-cnstn/model/design-cnstn-rvw-opnn-interface.ts b/apps/as/src/entities/design-cnstn/model/design-cnstn-rvw-opnn-interface.ts new file mode 100644 index 000000000..8b9841ee6 --- /dev/null +++ b/apps/as/src/entities/design-cnstn/model/design-cnstn-rvw-opnn-interface.ts @@ -0,0 +1,59 @@ +import type { PageableParams, PageResponse, RvwOpnnResponseExtension } from "@/shared/model"; + +/** 백엔드 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; + opnrNm?: string; + rvwRsltCn?: string; + rvwRsltAnmlyYn?: string; + atchFileGroupId?: string; +} + +/** 백엔드 DesignCnstnRvwOpnnSearchDto + Pageable */ +export interface DesignCnstnRvwOpnnSearchDto extends PageableParams { + designCnstnDocNo?: string; + opnrNm?: string; + opnrSeComCd?: string; + rvwRsltCn?: string; + rvwRsltAnmlyYn?: string; + atchFileGroupId?: string; +} + +/** 백엔드 DesignCnstnRvwOpnnResponseDto */ +export type DesignCnstnRvwOpnnResponseDto = Partial< + Pick +> & + Omit & + RvwOpnnResponseExtension; + +/** Controller public 메서드명·시그니처와 동일 */ +export interface DesignCnstnRvwOpnnService { + findPage( + searchDto: DesignCnstnRvwOpnnSearchDto, + ): Promise>; + findById(designCnstnDocNo: string, rvwOpnnSn: number): Promise; + save(body: DesignCnstnRvwOpnnSaveRequestDto): Promise; + update( + designCnstnDocNo: string, + rvwOpnnSn: number, + body: DesignCnstnRvwOpnnUpdateRequestDto, + ): Promise; + delete(designCnstnDocNo: string, rvwOpnnSn: number): Promise; +} diff --git a/apps/as/src/shared/config/api.ts b/apps/as/src/shared/config/api.ts index 0aab98ed6..98992a063 100644 --- a/apps/as/src/shared/config/api.ts +++ b/apps/as/src/shared/config/api.ts @@ -17,6 +17,11 @@ export const ENDPOINT = { ADSTDSTR: "/as-service/api/v1/adstdstr", FNRS_CNSLTN_MNG: "/as-service/api/v1/fnrs-cnsltn-mng", FNRS_SHAR_SE: "/as-service/api/v1/fnrs-shar-se-mng", + DESIGN_CNSTN: "/as-service/api/v1/design-cnstn", + DESIGN_CNSTN_BIZ_CST: "/as-service/api/v1/design-cnstn-biz-cst", + DESIGN_CNSTN_BIZ_SCL: "/as-service/api/v1/design-cnstn-biz-scl", + DESIGN_CNSTN_ESNTL_ATCH_FILE: "/as-service/api/v1/design-cnstn-esntl-atch-file", + DESIGN_CNSTN_RVW_OPNN: "/as-service/api/v1/design-cnstn-rvw-opnn", }, USER_SERVICE: { LOGIN: "/user-service/login", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fd99062b0..664b5ea7d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1042,6 +1042,9 @@ importers: js-cookie: specifier: ^3.0.5 version: 3.0.5 + lucide-react: + specifier: ^0.474.0 + version: 0.474.0(react@19.2.4) moment: specifier: ^2.30.1 version: 2.30.1 From 6e33a62ed9c1e745ddd49a5869cc8f1168ff9490 Mon Sep 17 00:00:00 2001 From: bicochan Date: Wed, 22 Jul 2026 11:34:38 +0900 Subject: [PATCH 2/2] =?UTF-8?q?[as]=20feat:=20=EC=84=A4=EA=B3=84=EC=9E=90?= =?UTF-8?q?=EB=AC=B8=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../design-cnstn/[id]/edit/page.tsx | 10 + .../design-cnstn/[id]/page.tsx | 10 + .../(with-base-layout)/design-cnstn/page.tsx | 3 + .../design-cnstn/regist/page.tsx | 12 + apps/as/src/entities/code/ui/code-select.tsx | 10 +- apps/as/src/pages/design-cnstn/index.ts | 5 + .../pages/design-cnstn/lib/button-config.ts | 118 ++++++ .../lib/design-cnstn-regist-schema.ts | 68 +++ .../design-cnstn/lib/form-data-mapper.ts | 97 +++++ .../design-cnstn/lib/review-opinion-utils.ts | 94 +++++ .../design-cnstn/lib/review-step-utils.ts | 12 + .../design-cnstn/lib/save-payload-utils.ts | 71 ++++ .../src/pages/design-cnstn/model/columns.tsx | 161 ++++++++ .../pages/design-cnstn/model/view-types.ts | 15 + .../ui/components/floating-action-buttons.tsx | 391 ++++++++++++++++++ .../design-cnstn/ui/design-edit-page.tsx | 41 ++ .../design-cnstn/ui/design-form-page.tsx | 209 ++++++++++ .../design-cnstn/ui/design-list-page.tsx | 152 +++++++ .../src/pages/design-cnstn/ui/design-list.tsx | 101 +++++ .../design-cnstn/ui/design-regist-page.tsx | 11 + .../design-cnstn/ui/design-view-page.tsx | 110 +++++ .../ui/sections/attachments-section.tsx | 112 +++++ .../ui/sections/basic-info-form-section.tsx | 159 +++++++ .../ui/sections/basic-info-section.tsx | 50 +++ .../ui/sections/business-scale-section.tsx | 70 ++++ .../ui/sections/contact-info-section.tsx | 56 +++ .../sections/tech-review-opinion-section.tsx | 342 +++++++++++++++ .../ui/components/floating-action-buttons.tsx | 14 +- .../pages/fnrs-cnsltn/ui/fnrs-view-page.tsx | 19 +- .../ui/sections/basic-info-section.tsx | 6 + .../sections/tech-review-opinion-section.tsx | 19 +- .../ui/components/floating-action-buttons.tsx | 14 +- .../pages/instl-sncn/ui/instl-view-page.tsx | 19 +- .../ui/sections/basic-info-section.tsx | 6 + .../sections/tech-review-opinion-section.tsx | 19 +- .../pbadms-sprt/lib/attachments-form-utils.ts | 2 +- .../pbadms-sprt/ui/checklist-form-section.tsx | 2 +- .../ui/contact-info-form-section.tsx | 2 +- 38 files changed, 2573 insertions(+), 39 deletions(-) create mode 100644 apps/as/app/(with-base-layout)/design-cnstn/[id]/edit/page.tsx create mode 100644 apps/as/app/(with-base-layout)/design-cnstn/[id]/page.tsx create mode 100644 apps/as/app/(with-base-layout)/design-cnstn/page.tsx create mode 100644 apps/as/app/(with-base-layout)/design-cnstn/regist/page.tsx create mode 100644 apps/as/src/pages/design-cnstn/index.ts create mode 100644 apps/as/src/pages/design-cnstn/lib/button-config.ts create mode 100644 apps/as/src/pages/design-cnstn/lib/design-cnstn-regist-schema.ts create mode 100644 apps/as/src/pages/design-cnstn/lib/form-data-mapper.ts create mode 100644 apps/as/src/pages/design-cnstn/lib/review-opinion-utils.ts create mode 100644 apps/as/src/pages/design-cnstn/lib/review-step-utils.ts create mode 100644 apps/as/src/pages/design-cnstn/lib/save-payload-utils.ts create mode 100644 apps/as/src/pages/design-cnstn/model/columns.tsx create mode 100644 apps/as/src/pages/design-cnstn/model/view-types.ts create mode 100644 apps/as/src/pages/design-cnstn/ui/components/floating-action-buttons.tsx create mode 100644 apps/as/src/pages/design-cnstn/ui/design-edit-page.tsx create mode 100644 apps/as/src/pages/design-cnstn/ui/design-form-page.tsx create mode 100644 apps/as/src/pages/design-cnstn/ui/design-list-page.tsx create mode 100644 apps/as/src/pages/design-cnstn/ui/design-list.tsx create mode 100644 apps/as/src/pages/design-cnstn/ui/design-regist-page.tsx create mode 100644 apps/as/src/pages/design-cnstn/ui/design-view-page.tsx create mode 100644 apps/as/src/pages/design-cnstn/ui/sections/attachments-section.tsx create mode 100644 apps/as/src/pages/design-cnstn/ui/sections/basic-info-form-section.tsx create mode 100644 apps/as/src/pages/design-cnstn/ui/sections/basic-info-section.tsx create mode 100644 apps/as/src/pages/design-cnstn/ui/sections/business-scale-section.tsx create mode 100644 apps/as/src/pages/design-cnstn/ui/sections/contact-info-section.tsx create mode 100644 apps/as/src/pages/design-cnstn/ui/sections/tech-review-opinion-section.tsx diff --git a/apps/as/app/(with-base-layout)/design-cnstn/[id]/edit/page.tsx b/apps/as/app/(with-base-layout)/design-cnstn/[id]/edit/page.tsx new file mode 100644 index 000000000..e5df52e22 --- /dev/null +++ b/apps/as/app/(with-base-layout)/design-cnstn/[id]/edit/page.tsx @@ -0,0 +1,10 @@ +import { DesignEditPage } from "@/pages/design-cnstn"; + +interface PageProps { + params: Promise<{ id: string }>; +} + +export default async function Page({ params }: PageProps) { + const { id } = await params; + return ; +} diff --git a/apps/as/app/(with-base-layout)/design-cnstn/[id]/page.tsx b/apps/as/app/(with-base-layout)/design-cnstn/[id]/page.tsx new file mode 100644 index 000000000..1b0fc983c --- /dev/null +++ b/apps/as/app/(with-base-layout)/design-cnstn/[id]/page.tsx @@ -0,0 +1,10 @@ +import { DesignViewPage } from "@/pages/design-cnstn"; + +interface PageProps { + params: Promise<{ id: string }>; +} + +export default async function Page({ params }: PageProps) { + const { id } = await params; + return ; +} diff --git a/apps/as/app/(with-base-layout)/design-cnstn/page.tsx b/apps/as/app/(with-base-layout)/design-cnstn/page.tsx new file mode 100644 index 000000000..cced4a877 --- /dev/null +++ b/apps/as/app/(with-base-layout)/design-cnstn/page.tsx @@ -0,0 +1,3 @@ +import { DesignListPage } from "@/pages/design-cnstn"; + +export default DesignListPage; diff --git a/apps/as/app/(with-base-layout)/design-cnstn/regist/page.tsx b/apps/as/app/(with-base-layout)/design-cnstn/regist/page.tsx new file mode 100644 index 000000000..567b634d5 --- /dev/null +++ b/apps/as/app/(with-base-layout)/design-cnstn/regist/page.tsx @@ -0,0 +1,12 @@ +import { DesignRegistPage } from "@/pages/design-cnstn"; +import { parseTaskSeComCdFromApi } from "@/pages/design-cnstn/lib/design-cnstn-regist-schema"; + +interface PageProps { + searchParams: Promise<{ taskSeComCd?: string }>; +} + +export default async function Page({ searchParams }: PageProps) { + const params = await searchParams; + const taskSeComCd = parseTaskSeComCdFromApi(params.taskSeComCd); + return ; +} diff --git a/apps/as/src/entities/code/ui/code-select.tsx b/apps/as/src/entities/code/ui/code-select.tsx index e803e9285..74d011bcd 100644 --- a/apps/as/src/entities/code/ui/code-select.tsx +++ b/apps/as/src/entities/code/ui/code-select.tsx @@ -18,13 +18,7 @@ interface CodeSelectProps extends React.ComponentPropsWithoutRef className?: string; } -function CodeSelect({ - upCd, - useYn, - placeholder = "선택", - className, - ...props -}: CodeSelectProps) { +function CodeSelect({ upCd, useYn, placeholder = "선택", className, ...props }: CodeSelectProps) { const listQuery = useCodeList(useYn ? undefined : upCd); const listByUseYnQuery = useCodeListByUseYn(useYn ? upCd : undefined, useYn ?? ""); @@ -34,7 +28,7 @@ function CodeSelect({ return ( { + setSidoComCd(value === ALL_SELECT_VALUE ? "" : value); + setSggComCd(""); + }} + > + + + + + 전체 + {sidoOptions.map((option) => ( + + {option.adstDstrNm} + + ))} + + + +
+ + +
+
+ + +
+
+ + setBizNm(event.target.value)} + placeholder="사업명" + /> +
+ + + + + + + ); +} + +export { DesignListPage }; diff --git a/apps/as/src/pages/design-cnstn/ui/design-list.tsx b/apps/as/src/pages/design-cnstn/ui/design-list.tsx new file mode 100644 index 000000000..8a0986379 --- /dev/null +++ b/apps/as/src/pages/design-cnstn/ui/design-list.tsx @@ -0,0 +1,101 @@ +"use client"; + +import { useMemo, useState } from "react"; +import { useRouter } from "next/navigation"; + +import { getCoreRowModel, type PaginationState } from "@tanstack/react-table"; + +import { useDataTable, useUserStore } from "@/shared/lib"; +import { + type DesignCnstnMngResponseDto, + type DesignCnstnMngSearchDto, + useDesignCnstnMngPage, +} from "@/entities/design-cnstn"; +import { TaskSePickerDialog } from "@/widgets/pbadms-sprt"; +import { Button } from "@common/components"; + +import { formatTaskSeComCdForApi } from "../lib/design-cnstn-regist-schema"; +import { createDesignCnstnColumns } from "../model/columns"; + +interface DesignListProps { + searchParams: DesignCnstnMngSearchDto; + pagination: PaginationState; + onPaginationChange: (pagination: PaginationState) => void; +} + +function DesignList({ searchParams, pagination, onPaginationChange }: DesignListProps) { + const userInfo = useUserStore((state) => state.userInfo); + const router = useRouter(); + const [isTaskSeDialogOpen, setIsTaskSeDialogOpen] = useState(false); + + const queryParams = useMemo( + () => ({ + bizNm: searchParams.bizNm, + taskSeComCd: searchParams.taskSeComCd, + admdstCd: searchParams.admdstCd, + page: pagination.pageIndex, + size: pagination.pageSize, + sort: searchParams.sort ?? "designCnstnDocNo,desc", + }), + [searchParams, pagination], + ); + + const pageQuery = useDesignCnstnMngPage(queryParams); + const columns = useMemo(() => createDesignCnstnColumns(), []); + + const handleRowClick = (row: DesignCnstnMngResponseDto) => { + const canEdit = row.prgrsSttsComCd === "CV" && row.rgtr === userInfo?.userUnqId; + + router.push( + canEdit + ? `/design-cnstn/${row.designCnstnDocNo}/edit` + : `/design-cnstn/${row.designCnstnDocNo}`, + ); + }; + + const { tableComponent } = useDataTable({ + query: pageQuery, + tableOptions: { + columns, + state: { pagination }, + onPaginationChange: (updater) => { + const next = typeof updater === "function" ? updater(pagination) : updater; + onPaginationChange(next); + }, + getCoreRowModel: getCoreRowModel(), + manualPagination: true, + }, + dataTableOptions: { + viewPagination: true, + onRowClick: handleRowClick, + }, + }); + + const handleConfirmTaskSe = (selectedCodes: string[]) => { + setIsTaskSeDialogOpen(false); + router.push(`/design-cnstn/regist?taskSeComCd=${formatTaskSeComCdForApi(selectedCodes)}`); + }; + + return ( + <> +
+
+

설계자문 목록

+ +
+ {tableComponent} +
+ + + ); +} + +export { DesignList }; diff --git a/apps/as/src/pages/design-cnstn/ui/design-regist-page.tsx b/apps/as/src/pages/design-cnstn/ui/design-regist-page.tsx new file mode 100644 index 000000000..fa8130898 --- /dev/null +++ b/apps/as/src/pages/design-cnstn/ui/design-regist-page.tsx @@ -0,0 +1,11 @@ +import { DesignFormPage } from "./design-form-page"; + +interface DesignRegistPageProps { + taskSeComCd?: string[]; +} + +function DesignRegistPage({ taskSeComCd }: DesignRegistPageProps) { + return ; +} + +export { DesignRegistPage }; diff --git a/apps/as/src/pages/design-cnstn/ui/design-view-page.tsx b/apps/as/src/pages/design-cnstn/ui/design-view-page.tsx new file mode 100644 index 000000000..aefd42317 --- /dev/null +++ b/apps/as/src/pages/design-cnstn/ui/design-view-page.tsx @@ -0,0 +1,110 @@ +"use client"; + +import { useMemo, useRef } from "react"; + +import { useUserStore } from "@/shared/lib"; +import { useDesignCnstnMng } from "@/entities/design-cnstn"; +import { ElaprDocPrgrsStatus } from "@/entities/elapr"; +import { useRvwMnpwMngLoginUserList } from "@/entities/rvw-mnpw"; +import { SectionCollapsible } from "@/widgets/pbadms-sprt"; +import { Skeleton } from "@common/components"; + +import { needsRoleUser01AtrzResolve } from "../lib/button-config"; +import { getEditableReviewStep } from "../lib/review-step-utils"; + +import { FloatingActionButtons } from "./components/floating-action-buttons"; +import { AttachmentsSection } from "./sections/attachments-section"; +import { BasicInfoSection } from "./sections/basic-info-section"; +import { BusinessScaleSection } from "./sections/business-scale-section"; +import { ContactInfoSection } from "./sections/contact-info-section"; +import { + TechReviewOpinionSection, + type TechReviewOpinionSectionHandle, +} from "./sections/tech-review-opinion-section"; + +interface DesignViewPageProps { + id: string; +} + +function DesignViewPage({ id }: DesignViewPageProps) { + const userInfo = useUserStore((state) => state.userInfo); + const techReviewOpinionRef = useRef(null); + const { data, isLoading, isError } = useDesignCnstnMng(id); + + const prgrsSttsComCd = data?.detDto?.prgrsSttsComCd || ""; + const needsAtrzResolve = needsRoleUser01AtrzResolve(userInfo?.dtlInstCd, prgrsSttsComCd); + const { data: rvwLoginUserData, isLoading: isRvwLoginUserLoading } = useRvwMnpwMngLoginUserList( + {}, + { enabled: needsAtrzResolve }, + ); + const atrzSeComCdList = useMemo( + () => rvwLoginUserData?.content?.map((item) => item.atrzSeComCd) ?? [], + [rvwLoginUserData], + ); + + if (isLoading) { + return ( +
+
+ + +
+
+ + +
+
+ + +
+
+ ); + } + + if (isError || !data?.detDto) { + return
상세 정보를 찾을 수 없습니다.
; + } + + const detDto = data.detDto; + const bizSclList = data.bizSclList ?? []; + const esntlFileList = data.esntlFileList ?? []; + const etcFileList = data.etcFileList ?? []; + const rvwList = data.rvwList ?? []; + + const editableReviewStep = getEditableReviewStep( + userInfo?.dtlInstCd, + detDto.prgrsSttsComCd || "", + ); + + return ( +
+ + + + + + + + + +
+ ); +} + +export { DesignViewPage }; diff --git a/apps/as/src/pages/design-cnstn/ui/sections/attachments-section.tsx b/apps/as/src/pages/design-cnstn/ui/sections/attachments-section.tsx new file mode 100644 index 000000000..082881231 --- /dev/null +++ b/apps/as/src/pages/design-cnstn/ui/sections/attachments-section.tsx @@ -0,0 +1,112 @@ +"use client"; + +import { formatFileSize } from "@/shared/lib/file-utils"; +import { useDownloadAttachmentByAtflCd } from "@/entities/attach"; +import type { + AtflMngClientResponseDto, + EsntlAtchFileMngTreeResponseDto, +} from "@/entities/design-cnstn"; +import { SectionCollapsible } from "@/widgets/pbadms-sprt"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components"; + +interface AttachmentsSectionProps { + esntlFileList: EsntlAtchFileMngTreeResponseDto[]; + etcFileList: AtflMngClientResponseDto[]; +} + +function AttachmentsSection({ esntlFileList, etcFileList }: AttachmentsSectionProps) { + const { mutate: downloadAttachment } = useDownloadAttachmentByAtflCd(); + + return ( + <> + {esntlFileList.length > 0 && ( + +
+ + + + 필수 자료명 + 파일명 + 크기 + + + + {esntlFileList.map((file) => ( + + + {file.esntlAtchFileNm} + {file.esntlAtchYn === "Y" ? ( + * + ) : null} + + + {file.atflCd ? ( + downloadAttachment(file.atflCd ?? "")} + target="_blank" + rel="noreferrer" + > + {file.orgnlFileNm || "다운로드"} + + ) : ( + 첨부없음 + )} + + {formatFileSize(file.atflFlieSz)} + + ))} + +
+
+
+ )} + + +
+ + + + 파일명 + 크기 + + + + {etcFileList.length === 0 ? ( + + + 등록된 기타첨부파일이 없습니다. + + + ) : ( + etcFileList.map((file) => ( + + + {file.atflCd ? ( + downloadAttachment(file.atflCd)} + target="_blank" + rel="noreferrer" + > + {file.orgnlFileNm || file.physFileNm || file.atflId} + + ) : ( + {file.orgnlFileNm || file.physFileNm || file.atflId} + )} + + + {formatFileSize(file.atflFlieSz)} + + + )) + )} + +
+
+
+ + ); +} + +export { AttachmentsSection }; diff --git a/apps/as/src/pages/design-cnstn/ui/sections/basic-info-form-section.tsx b/apps/as/src/pages/design-cnstn/ui/sections/basic-info-form-section.tsx new file mode 100644 index 000000000..124370ddc --- /dev/null +++ b/apps/as/src/pages/design-cnstn/ui/sections/basic-info-form-section.tsx @@ -0,0 +1,159 @@ +"use client"; + +import { useState } from "react"; +import { Controller, useFormContext } from "react-hook-form"; + +import { useUserStore } from "@/shared/lib"; +import { Field, FieldError, FieldGroup } from "@/shared/ui/field"; +import { findCodeLabel, useCodeList } from "@/entities/code"; +import { type BnsApplyData, BnsPickerDialog } from "@/widgets/bns-picker"; +import { SectionCollapsible } from "@/widgets/pbadms-sprt"; +import { + Button, + Input, + Table, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@common/components"; + +import type { DesignCnstnRegistFormData } from "../../lib/design-cnstn-regist-schema"; + +function BasicInfoFormSection() { + const userInfo = useUserStore((state) => state.userInfo); + const { control, watch, setValue } = useFormContext(); + const [isPickerOpen, setIsPickerOpen] = useState(false); + const taskSeComCd = watch("designCnstnMng.taskSeComCd"); + const { data: taskOptions } = useCodeList("CNFM"); + + function handleBnsSelect(data: BnsApplyData) { + setValue("designCnstnMng.bizNm", data.bizNm); + setValue("designCnstnMng.bizBgngYr", data.bizBgngYr); + setValue("designCnstnMng.bizEndYr", data.bizEndYr); + setValue("designCnstnMng.bizCn", data.bizCn); + } + + const taskSeComCdLabel = + taskSeComCd + .map((code) => findCodeLabel(taskOptions, code)) + .filter(Boolean) + .join(", ") || "-"; + + return ( + + + + + + + + + + + + 사업명 + + + ( + +
+ + +
+ +
+ )} + /> +
+
+ + 사업기간 + + + ( + + + + + )} + /> + ~ + ( + + + + + )} + /> + + + + + 사업내용 + + ( + + + + + )} + /> + + + + 구분 + {taskSeComCdLabel} + 신청 지자체 + {userInfo?.instSeNm} + +
+
+ + setIsPickerOpen(false)} + onSelect={handleBnsSelect} + /> +
+ ); +} + +export { BasicInfoFormSection }; diff --git a/apps/as/src/pages/design-cnstn/ui/sections/basic-info-section.tsx b/apps/as/src/pages/design-cnstn/ui/sections/basic-info-section.tsx new file mode 100644 index 000000000..c5db62a50 --- /dev/null +++ b/apps/as/src/pages/design-cnstn/ui/sections/basic-info-section.tsx @@ -0,0 +1,50 @@ +"use client"; + +import type { DesignCnstnMngResponseDto } from "@/entities/design-cnstn"; +import { SectionCollapsible } from "@/widgets/pbadms-sprt"; +import { Table, TableCell, TableHead, TableHeader, TableRow } from "@common/components"; + +interface BasicInfoSectionProps { + det: DesignCnstnMngResponseDto; +} + +function BasicInfoSection({ det }: BasicInfoSectionProps) { + return ( + + + + + + + + + + + 사업명 + {det.bizNm || "-"} + 사업기간 + {(det.bizBgngYr || "-") + " ~ " + (det.bizEndYr || "-")} + + + 사업내용 + {det.bizCn || "-"} + + + 환경청구분 + {det.envoSeCdNm || "-"} + 구분 + {det.taskSeCdNm || "-"} + + + 신청지자체 + + {det.sido || "-"} {det.gugun || "-"} + + + +
+
+ ); +} + +export { BasicInfoSection }; diff --git a/apps/as/src/pages/design-cnstn/ui/sections/business-scale-section.tsx b/apps/as/src/pages/design-cnstn/ui/sections/business-scale-section.tsx new file mode 100644 index 000000000..c18e6cf3e --- /dev/null +++ b/apps/as/src/pages/design-cnstn/ui/sections/business-scale-section.tsx @@ -0,0 +1,70 @@ +"use client"; + +import type { BizSclTreeDto } from "@/entities/design-cnstn"; +import { fmtNumber, indentPx, SectionCollapsible } from "@/widgets/pbadms-sprt"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components"; + +interface BusinessScaleSectionProps { + bizSclList: BizSclTreeDto[]; +} + +function BusinessScaleSection({ bizSclList }: BusinessScaleSectionProps) { + return ( + +
+ + + + 구분 + 기본계획(A) + 예산반영(B) + 금회설계시(C) + 증감(C-B) + + + + {bizSclList.length === 0 ? ( + + + 조회된 사업규모 항목이 없습니다. + + + ) : ( + bizSclList.map((node) => { + const pl = indentPx(node.indCnstpSeCdNm); + const bscPlanQty = Number(node.bscPlanQty) || 0; + const budgetReflct = Number(node.bgtRfltQty) || 0; + const tstmDesignQty = Number(node.tstmDesignQty) || 0; + const diff = tstmDesignQty - budgetReflct; + + return ( + + + {node.cnstpSeCdNm} + + +
{fmtNumber(bscPlanQty)}
+
+ +
{fmtNumber(budgetReflct)}
+
+ +
{fmtNumber(tstmDesignQty)}
+
+ +
+ {diff > 0 ? `+${fmtNumber(diff)}` : fmtNumber(diff)} +
+
+
+ ); + }) + )} +
+
+
+
+ ); +} + +export { BusinessScaleSection }; diff --git a/apps/as/src/pages/design-cnstn/ui/sections/contact-info-section.tsx b/apps/as/src/pages/design-cnstn/ui/sections/contact-info-section.tsx new file mode 100644 index 000000000..ae495450e --- /dev/null +++ b/apps/as/src/pages/design-cnstn/ui/sections/contact-info-section.tsx @@ -0,0 +1,56 @@ +"use client"; + +import type { DesignCnstnMngResponseDto } from "@/entities/design-cnstn"; +import { SectionCollapsible } from "@/widgets/pbadms-sprt"; +import { Table, TableCell, TableHead, TableHeader, TableRow } from "@common/components"; + +interface ContactInfoSectionProps { + det: DesignCnstnMngResponseDto; +} + +function ContactInfoSection({ det }: ContactInfoSectionProps) { + return ( + + + + {det.aplcntNm && ( + <> + + 신청자 + 이름 + {det.aplcntNm || "-"} + 연락처 + {det.aplcntMblTelno || "-"} + + + 소속 + {det.aplcntOgdpNm || "-"} + 이메일 + {det.aplcntEmlAddr || "-"} + + + )} + {det.dsgnrNm && ( + <> + + 설계자 + 이름 + {det.dsgnrNm || "-"} + 연락처 + {det.dsgnrTelno || "-"} + + + 소속 + {det.dsgnrOgdpNm || "-"} + 이메일 + {det.dsgnrEmlAddr || "-"} + + + )} + +
+
+ ); +} + +export { ContactInfoSection }; diff --git a/apps/as/src/pages/design-cnstn/ui/sections/tech-review-opinion-section.tsx b/apps/as/src/pages/design-cnstn/ui/sections/tech-review-opinion-section.tsx new file mode 100644 index 000000000..9c6985ffb --- /dev/null +++ b/apps/as/src/pages/design-cnstn/ui/sections/tech-review-opinion-section.tsx @@ -0,0 +1,342 @@ +"use client"; + +import { forwardRef, useEffect, useImperativeHandle, useState } from "react"; + +import { PROXY_HOST } from "@/shared/config"; +import { dayjs, getDefaultFileKey, useTus } from "@/shared/lib"; +import { Field, FieldLabel, FieldSet } from "@/shared/ui/field"; +import { attachService } from "@/entities/attach"; +import type { DesignCnstnRvwOpnnResponseDto } from "@/entities/design-cnstn"; +import { SectionCollapsible } from "@/widgets/pbadms-sprt"; +import { + Button, + RadioGroup, + RadioGroupItem, + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, + Textarea, +} from "@common/components"; + +import type { + ReviewOpinionFormData, + ReviewOpinionGetFormDataResult, +} from "../../lib/review-opinion-utils"; +import type { ReviewStepCode } from "../../model/view-types"; +import { REVIEW_STEP_LABELS } from "../../model/view-types"; + +const RVW_OPNN_FILE_KEY_PREFIX = "rvw-opnn::"; + +interface TechReviewOpinionSectionProps { + rvwList: DesignCnstnRvwOpnnResponseDto[]; + editableReviewStep?: ReviewStepCode; + atrzSeComCdList?: string[]; + isAtrzLoading?: boolean; +} + +export interface TechReviewOpinionSectionHandle { + getFormData: () => ReviewOpinionGetFormDataResult | null; + resetForm: () => void; +} + +interface TechReviewOpinionTableProps { + items: DesignCnstnRvwOpnnResponseDto[]; +} + +interface UploadedOpinionFile { + fileName: string; + fileKey: string; + atflId?: string; + atflCd?: string; +} + +const EMPTY_FORM_DATA: ReviewOpinionFormData = { + rvwRsltCn: "", + rvwRsltAnmlyYn: "", + atchFileGroupId: "", + opnrSeComCd: "", +}; + +function createRvwOpnnUploadKey(file: File) { + return `${RVW_OPNN_FILE_KEY_PREFIX}${getDefaultFileKey(file)}`; +} + +function hasFormContent(formData: ReviewOpinionFormData) { + return Boolean(formData.rvwRsltCn.trim() || formData.rvwRsltAnmlyYn || formData.atchFileGroupId); +} + +const TechReviewOpinionSection = forwardRef< + TechReviewOpinionSectionHandle, + TechReviewOpinionSectionProps +>(function TechReviewOpinionSection({ + rvwList, + editableReviewStep, + atrzSeComCdList = [], + isAtrzLoading = false, +}, ref) { + const canWriteOpinion = !isAtrzLoading && atrzSeComCdList.includes("ATRZ02"); + const [formData, setFormData] = useState({ + ...EMPTY_FORM_DATA, + opnrSeComCd: editableReviewStep ?? "", + }); + const [uploadedFile, setUploadedFile] = useState(null); + + useEffect(() => { + setFormData((prev) => ({ + ...prev, + opnrSeComCd: editableReviewStep ?? "", + })); + }, [editableReviewStep]); + + const { uploadFileAsync, isUploading, uploads } = useTus({ + onSuccess: (atflCd, atflId, _file, fileKey) => { + if (!atflCd || !atflId) return; + + setFormData((prev) => ({ ...prev, atchFileGroupId: atflCd })); + setUploadedFile((prev) => (prev?.fileKey === fileKey ? { ...prev, atflCd, atflId } : prev)); + }, + onError: (_error, _file, fileKey) => { + setUploadedFile((prev) => (prev?.fileKey === fileKey ? null : prev)); + }, + }); + + useImperativeHandle( + ref, + () => ({ + getFormData: () => { + if (!canWriteOpinion || !editableReviewStep || !hasFormContent(formData)) { + return null; + } + + return { + formData: { + ...formData, + opnrSeComCd: editableReviewStep, + }, + rvwList, + }; + }, + resetForm: () => { + setFormData({ + ...EMPTY_FORM_DATA, + opnrSeComCd: editableReviewStep ?? "", + }); + setUploadedFile(null); + }, + }), + [canWriteOpinion, editableReviewStep, formData, rvwList], + ); + + const kecoRvwList = rvwList.filter((item) => item.opnrSeComCd === "KECO"); + const hasKecoSection = + kecoRvwList.length > 0 || (editableReviewStep === "KECO" && canWriteOpinion); + + if (!hasKecoSection) { + return null; + } + + async function handleFileUpload(files: FileList | null) { + if (!files || files.length === 0) return; + + const file = files[0]; + const fileKey = createRvwOpnnUploadKey(file); + const existingAtflCd = formData.atchFileGroupId; + + setUploadedFile({ fileName: file.name, fileKey }); + + try { + await uploadFileAsync(file, { + fileKey, + metadata: existingAtflCd ? { atflCd: existingAtflCd } : undefined, + }); + } catch { + // onError callback handles cleanup + } + } + + async function handleFileRemove() { + if (!uploadedFile) return; + + uploads[uploadedFile.fileKey]?.abort(); + + if (uploadedFile.atflId) { + try { + await attachService.deleteAttachmentByAtflId(uploadedFile.atflId); + } catch (error) { + console.error("검토의견 첨부파일 삭제 실패:", error); + } + } + + setUploadedFile(null); + setFormData((prev) => ({ ...prev, atchFileGroupId: "" })); + } + + function renderOpinionForm(reviewStep: ReviewStepCode) { + if (editableReviewStep !== reviewStep || !canWriteOpinion) return null; + + const uploading = uploadedFile ? (isUploading[uploadedFile.fileKey] ?? false) : false; + + return ( +
+

검토의견 작성

+ + + + 의견내용 + +