diff --git a/apps/as/src/entities/attach/api/attach-service.ts b/apps/as/src/entities/attach/api/attach-service.ts index 0af0a916d..ceba5de3f 100644 --- a/apps/as/src/entities/attach/api/attach-service.ts +++ b/apps/as/src/entities/attach/api/attach-service.ts @@ -16,7 +16,14 @@ async function downloadAttachmentByAtflCd(atflCd: string): Promise { + return await client.get(`${ENDPOINT.ATTACH_SERVICE.DOWNLOAD_BY_ATFL_ID}/${atflId}`, { + responseType: "blob", + }); +} + export const attachService = { deleteAttachmentByAtflId, downloadAttachmentByAtflCd, + downloadAttachmentByAtflId, } satisfies AttachService; diff --git a/apps/as/src/entities/attach/api/queries.ts b/apps/as/src/entities/attach/api/queries.ts index ed9efefbf..09a1ab1d9 100644 --- a/apps/as/src/entities/attach/api/queries.ts +++ b/apps/as/src/entities/attach/api/queries.ts @@ -1,4 +1,5 @@ import { useMutation } from "@tanstack/react-query"; +import type { AxiosResponse } from "axios"; import { ENDPOINT } from "@/shared/config"; @@ -8,6 +9,22 @@ import { attachService } from "./attach-service"; const attachBaseKey = [ENDPOINT.ATTACH_SERVICE.ATTACHMENTS] as const; const attachDownloadBaseKey = [ENDPOINT.ATTACH_SERVICE.DOWNLOAD] as const; +const attachDownloadByAtflIdKey = [ENDPOINT.ATTACH_SERVICE.DOWNLOAD_BY_ATFL_ID] as const; + +function triggerBlobDownload(response: AxiosResponse) { + const filename = parseFilenameFromResponse(response); + const blob = new Blob([response.data]); + const url = window.URL.createObjectURL(blob); + const link = document.createElement("a"); + + link.href = url; + link.download = filename; + document.body.appendChild(link); + link.click(); + + window.URL.revokeObjectURL(url); + document.body.removeChild(link); +} export function useDeleteAttachment() { return useMutation({ @@ -21,19 +38,17 @@ export function useDownloadAttachmentByAtflCd() { mutationKey: [...attachDownloadBaseKey, "download"], mutationFn: async (atflCd: string) => { const response = await attachService.downloadAttachmentByAtflCd(atflCd); - const filename = parseFilenameFromResponse(response); - - const blob = new Blob([response.data]); - const url = window.URL.createObjectURL(blob); - const link = document.createElement("a"); - - link.href = url; - link.download = filename; - document.body.appendChild(link); - link.click(); - - window.URL.revokeObjectURL(url); - document.body.removeChild(link); + triggerBlobDownload(response); + }, + }); +} + +export function useDownloadAttachmentByAtflId() { + return useMutation({ + mutationKey: [...attachDownloadByAtflIdKey, "download"], + mutationFn: async (atflId: string) => { + const response = await attachService.downloadAttachmentByAtflId(atflId); + triggerBlobDownload(response); }, }); } diff --git a/apps/as/src/entities/attach/index.ts b/apps/as/src/entities/attach/index.ts index 37258e6e5..c2c771b43 100644 --- a/apps/as/src/entities/attach/index.ts +++ b/apps/as/src/entities/attach/index.ts @@ -1,3 +1,7 @@ export { attachService } from "./api/attach-service"; -export { useDeleteAttachment, useDownloadAttachmentByAtflCd } from "./api/queries"; +export { + useDeleteAttachment, + useDownloadAttachmentByAtflCd, + useDownloadAttachmentByAtflId, +} from "./api/queries"; export type { Attach, AttachService } from "./model/attach-interface"; diff --git a/apps/as/src/entities/attach/model/attach-interface.ts b/apps/as/src/entities/attach/model/attach-interface.ts index d6b0a08d2..f603a328e 100644 --- a/apps/as/src/entities/attach/model/attach-interface.ts +++ b/apps/as/src/entities/attach/model/attach-interface.ts @@ -19,4 +19,5 @@ export interface Attach { export interface AttachService { deleteAttachmentByAtflId: (atflId: string) => Promise; downloadAttachmentByAtflCd: (atflCd: string) => Promise; + downloadAttachmentByAtflId: (atflId: string) => Promise; } diff --git a/apps/as/src/pages/design-cnstn/lib/form-data-mapper.ts b/apps/as/src/pages/design-cnstn/lib/form-data-mapper.ts index 9ba03d856..3150bfaa7 100644 --- a/apps/as/src/pages/design-cnstn/lib/form-data-mapper.ts +++ b/apps/as/src/pages/design-cnstn/lib/form-data-mapper.ts @@ -1,23 +1,10 @@ import type { IUserInfo } from "@/shared/store"; import 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"; -function mapEsntlAtchFileList(esntlFileList: DesignCnstnMngDetResponseDto["esntlFileList"]) { - const esntlMap = new Map(); - - for (const item of esntlFileList ?? []) { - if (!item.esntlAtchFileId || !item.atflCd) continue; - esntlMap.set(item.esntlAtchFileId, item.atflCd); - } - - return Array.from(esntlMap.entries()).map(([esntlAtchFileId, atchFileGroupId]) => ({ - esntlAtchFileId, - atchFileGroupId, - })); -} - export function mapDetailToFormData(data: DesignCnstnMngDetResponseDto): DesignCnstnRegistFormData { const detDto = data.detDto; if (!detDto) { @@ -63,7 +50,7 @@ export function mapDetailToFormData(data: DesignCnstnMngDetResponseDto): DesignC thtiDsgn: item.tstmDesignQty, diff: item.tstmDesignQty - item.bgtRfltQty, })), - esntlAtchFileList: mapEsntlAtchFileList(data.esntlFileList), + esntlAtchFileList: toEsntlAtchFileSaveList(data.esntlFileList), privacyConsent: "Y", }; } diff --git a/apps/as/src/pages/design-cnstn/lib/save-payload-utils.ts b/apps/as/src/pages/design-cnstn/lib/save-payload-utils.ts index 48fff8b26..48da7a731 100644 --- a/apps/as/src/pages/design-cnstn/lib/save-payload-utils.ts +++ b/apps/as/src/pages/design-cnstn/lib/save-payload-utils.ts @@ -3,9 +3,8 @@ import type { BzScaleItem, DesignCnstnApplySaveRequestDto, DesignCnstnMngDetResponseDto, - EsntlAtchFileItem, - EsntlAtchFileMngTreeResponseDto, } from "@/entities/design-cnstn"; +import { toEsntlAtchFileSaveList } from "@/widgets/pbadms-sprt"; import type { ReviewStepCode } from "../model/view-types"; @@ -22,22 +21,6 @@ export function toBzScaleSaveDto(bizSclList: BizSclTreeDto[]): BzScaleItem[] { })); } -export function toEsntlAtchFileSaveDto( - esntlFileList: EsntlAtchFileMngTreeResponseDto[], -): EsntlAtchFileItem[] { - const esntlMap = new Map(); - - for (const item of esntlFileList) { - if (!item.esntlAtchFileId || !item.atflCd) continue; - esntlMap.set(item.esntlAtchFileId, item.atflCd); - } - - return Array.from(esntlMap.entries()).map(([esntlAtchFileId, atchFileGroupId]) => ({ - esntlAtchFileId, - atchFileGroupId, - })); -} - export function buildSavePayload( data: DesignCnstnMngDetResponseDto, prgrsSttsComCd: string, @@ -50,7 +33,7 @@ export function buildSavePayload( return { designCnstnMng: { ...detDto, prgrsSttsComCd }, bzScaleList: toBzScaleSaveDto(data.bizSclList ?? []), - esntlAtchFileList: toEsntlAtchFileSaveDto(data.esntlFileList ?? []), + esntlAtchFileList: toEsntlAtchFileSaveList(data.esntlFileList), }; } 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 index cc4315025..6915214d9 100644 --- a/apps/as/src/pages/design-cnstn/ui/design-view-page.tsx +++ b/apps/as/src/pages/design-cnstn/ui/design-view-page.tsx @@ -6,7 +6,7 @@ import { useUserStore } from "@/shared/lib"; import { useDesignCnstnMng } from "@/entities/design-cnstn"; import { ElaprDocPrgrsStatus } from "@/entities/elapr"; import { useRvwMnpwMngLoginUserList } from "@/entities/rvw-mnpw"; -import { ProgressStatusSection } from "@/widgets/pbadms-sprt"; +import { AttachmentsViewSection, ProgressStatusSection } from "@/widgets/pbadms-sprt"; import { Skeleton } from "@common/components"; import { needsRoleUser01AtrzResolve } from "../lib/button-config"; @@ -14,7 +14,6 @@ import { getEditableReviewStep } from "../lib/review-step-utils"; import { PROGRESS_STATUS_COLUMNS } from "../model/columns"; 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"; @@ -91,7 +90,7 @@ function DesignViewPage({ id }: DesignViewPageProps) { - + - {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.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/fnrs-cnsltn/lib/form-data-mapper.ts b/apps/as/src/pages/fnrs-cnsltn/lib/form-data-mapper.ts index d30765961..09e4817f5 100644 --- a/apps/as/src/pages/fnrs-cnsltn/lib/form-data-mapper.ts +++ b/apps/as/src/pages/fnrs-cnsltn/lib/form-data-mapper.ts @@ -1,6 +1,6 @@ import type { IUserInfo } from "@/shared/store"; import type { FnrsCnsltnMngDetResponseDto } from "@/entities/fnrs-cnsltn"; -import { mapFnrsSharDetToFormList } from "@/widgets/pbadms-sprt"; +import { mapFnrsSharDetToFormList, toEsntlAtchFileSaveList } from "@/widgets/pbadms-sprt"; import type { FnrsCnsltnRegistFormData } from "./fnrs-cnsltn-regist-schema"; import { parseTaskSeComCdFromApi } from "./fnrs-cnsltn-regist-schema"; @@ -10,20 +10,6 @@ function mapSelectedValue(value?: string | null): "Y" | "N" | null { return null; } -function mapEsntlAtchFileList(esntlFileList: FnrsCnsltnMngDetResponseDto["esntlFileList"]) { - const esntlMap = new Map(); - - for (const item of esntlFileList ?? []) { - if (!item.esntlAtchFileId || !item.atflCd) continue; - esntlMap.set(item.esntlAtchFileId, item.atflCd); - } - - return Array.from(esntlMap.entries()).map(([esntlAtchFileId, atchFileGroupId]) => ({ - esntlAtchFileId, - atchFileGroupId, - })); -} - export function mapDetailToFormData(data: FnrsCnsltnMngDetResponseDto): FnrsCnsltnRegistFormData { const detDto = data.detDto; if (!detDto) { @@ -94,7 +80,7 @@ export function mapDetailToFormData(data: FnrsCnsltnMngDetResponseDto): FnrsCnsl selected: mapSelectedValue(item.rvwRsltAnmlyYn), picRvwCn: item.picRvwCn, })) ?? [], - esntlAtchFileList: mapEsntlAtchFileList(esntlFileList), + esntlAtchFileList: toEsntlAtchFileSaveList(esntlFileList), splmntDmndList: splmntDmndList?.map((item) => ({ splmntDmndSn: item.splmntDmndSn, 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 fb34d7d0e..21e307f53 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 @@ -4,8 +4,6 @@ import type { BizSclTreeDto, BzScaleItem, CklstResultItem, - EsntlAtchFileItem, - EsntlAtchFileMngTreeResponseDto, FnrsCnsltnApplySaveRequestDto, FnrsCnsltnMngDetResponseDto, FnrsCnsltnMngResponseDto, @@ -14,6 +12,7 @@ import type { import { mapFnrsSharDetToFormList, type SplmntDmndDialogConfirmPayload, + toEsntlAtchFileSaveList, toFnrsSharSaveList, } from "@/widgets/pbadms-sprt"; @@ -44,22 +43,6 @@ export function toBizCstSaveDto(bizCstList: BizCstTreeDto[]): BizCstItem[] { })); } -export function toEsntlAtchFileSaveDto( - esntlFileList: EsntlAtchFileMngTreeResponseDto[], -): EsntlAtchFileItem[] { - const esntlMap = new Map(); - - for (const item of esntlFileList) { - if (!item.esntlAtchFileId || !item.atflCd) continue; - esntlMap.set(item.esntlAtchFileId, item.atflCd); - } - - return Array.from(esntlMap.entries()).map(([esntlAtchFileId, atchFileGroupId]) => ({ - esntlAtchFileId, - atchFileGroupId, - })); -} - export function buildSavePayload( data: FnrsCnsltnMngDetResponseDto, checklistFormState: ChecklistFormState, @@ -81,7 +64,7 @@ export function buildSavePayload( bzScaleList: toBzScaleSaveDto(bizSclList), bizCstList: toBizCstSaveDto(bizCstList), cklstResultList: toCklstSaveDto(cklstResultList, checklistFormState, reviewStep), - esntlAtchFileList: toEsntlAtchFileSaveDto(esntlFileList), + esntlAtchFileList: toEsntlAtchFileSaveList(esntlFileList), fnrsSharList: toFnrsSharSaveList(mapFnrsSharDetToFormList(data.fnrsSharList ?? [])), }; } diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/fnrs-view-page.tsx b/apps/as/src/pages/fnrs-cnsltn/ui/fnrs-view-page.tsx index 6fa957d44..96f3124b3 100644 --- a/apps/as/src/pages/fnrs-cnsltn/ui/fnrs-view-page.tsx +++ b/apps/as/src/pages/fnrs-cnsltn/ui/fnrs-view-page.tsx @@ -6,7 +6,7 @@ import { useUserStore } from "@/shared/lib"; import { ElaprDocPrgrsStatus } from "@/entities/elapr"; import { useFnrsCnsltnMng } from "@/entities/fnrs-cnsltn"; import { useRvwMnpwMngLoginUserList } from "@/entities/rvw-mnpw"; -import { ProgressStatusSection, SupplementDemandSection } from "@/widgets/pbadms-sprt"; +import { AttachmentsViewSection, ProgressStatusSection, SupplementDemandSection } from "@/widgets/pbadms-sprt"; import { Skeleton } from "@common/components"; import { needsRoleUser01AtrzResolve } from "../lib/button-config"; @@ -15,7 +15,6 @@ import { EMPTY_CKLST_RESULT_LIST, useChecklistFormState } from "../lib/use-check import { PROGRESS_STATUS_COLUMNS } from "../model/columns"; import { FloatingActionButtons } from "./components/floating-action-buttons"; -import { AttachmentsSection } from "./sections/attachments-section"; import { BasicInfoSection } from "./sections/basic-info-section"; import { BusinessCostSection } from "./sections/business-cost-section"; import { BusinessScaleSection } from "./sections/business-scale-section"; @@ -110,7 +109,7 @@ function FnrsViewPage({ id }: FnrsViewPageProps) { formState={checklistFormState} setFormState={setChecklistFormState} /> - + - {esntlFileList.length > 0 && ( - -
- - - - 필수 자료명 - 파일명 - 크기 - - - - {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.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/instl-sncn/lib/form-data-mapper.ts b/apps/as/src/pages/instl-sncn/lib/form-data-mapper.ts index a1e1242c5..e32309255 100644 --- a/apps/as/src/pages/instl-sncn/lib/form-data-mapper.ts +++ b/apps/as/src/pages/instl-sncn/lib/form-data-mapper.ts @@ -1,5 +1,6 @@ import type { IUserInfo } from "@/shared/store/user-store"; import 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"; @@ -9,20 +10,6 @@ function mapSelectedValue(value?: string | null): "Y" | "N" | null { return null; } -function mapEsntlAtchFileList(esntlFileList: InstlSncnMngDetResponse["esntlFileList"]) { - const esntlMap = new Map(); - - for (const item of esntlFileList) { - if (!item.esntlAtchFileId || !item.atflCd) continue; - esntlMap.set(item.esntlAtchFileId, item.atflCd); - } - - return Array.from(esntlMap.entries()).map(([esntlAtchFileId, atchFileGroupId]) => ({ - esntlAtchFileId, - atchFileGroupId, - })); -} - export function mapDetailToFormData(data: InstlSncnMngDetResponse): InstlSncnRegistFormData { const { detDto, bizSclList, bizCstList, cklstResultList, esntlFileList, splmntDmndList } = data; @@ -84,7 +71,7 @@ export function mapDetailToFormData(data: InstlSncnMngDetResponse): InstlSncnReg selected: mapSelectedValue(item.rvwRsltAnmlyYn), picRvwCn: item.picRvwCn, })) ?? [], - esntlAtchFileList: mapEsntlAtchFileList(esntlFileList), + esntlAtchFileList: toEsntlAtchFileSaveList(esntlFileList), splmntDmndList: splmntDmndList?.map((item) => ({ splmntDmndSn: item.splmntDmndSn, diff --git a/apps/as/src/pages/instl-sncn/lib/save-payload-utils.ts b/apps/as/src/pages/instl-sncn/lib/save-payload-utils.ts index bb9bc18ab..f2d647aea 100644 --- a/apps/as/src/pages/instl-sncn/lib/save-payload-utils.ts +++ b/apps/as/src/pages/instl-sncn/lib/save-payload-utils.ts @@ -1,16 +1,14 @@ import type { BizCstTreeDto, BizSclTreeDto, - EsntlAtchFileMngTreeResponseDto, InstlSncnApplySaveRequestDto, InstlSncnBizCstSaveRequestDto, InstlSncnBzScaleSaveDto, - InstlSncnEsntlAtchFileSaveDto, InstlSncnMngDetResponse, InstlSncnSplmntDmndSaveDto, ReviewStepCode, } from "@/entities/instl-sncn"; -import type { SplmntDmndDialogConfirmPayload } from "@/widgets/pbadms-sprt"; +import { type SplmntDmndDialogConfirmPayload, toEsntlAtchFileSaveList } from "@/widgets/pbadms-sprt"; import type { ChecklistFormState } from "../model/view-types"; @@ -40,22 +38,6 @@ export function toBizCstSaveDto(bizCstList: BizCstTreeDto[]): InstlSncnBizCstSav })); } -export function toEsntlAtchFileSaveDto( - esntlFileList: EsntlAtchFileMngTreeResponseDto[], -): InstlSncnEsntlAtchFileSaveDto[] { - const esntlMap = new Map(); - - for (const item of esntlFileList) { - if (!item.esntlAtchFileId || !item.atflCd) continue; - esntlMap.set(item.esntlAtchFileId, item.atflCd); - } - - return Array.from(esntlMap.entries()).map(([esntlAtchFileId, atchFileGroupId]) => ({ - esntlAtchFileId, - atchFileGroupId, - })); -} - export function buildSavePayload( data: InstlSncnMngDetResponse, checklistFormState: ChecklistFormState, @@ -69,7 +51,7 @@ export function buildSavePayload( bzScaleList: toBzScaleSaveDto(bizSclList), bizCstList: toBizCstSaveDto(bizCstList), cklstResultList: toCklstSaveDto(cklstResultList, checklistFormState, reviewStep), - esntlAtchFileList: toEsntlAtchFileSaveDto(esntlFileList), + esntlAtchFileList: toEsntlAtchFileSaveList(esntlFileList), }; } diff --git a/apps/as/src/pages/instl-sncn/ui/instl-view-page.tsx b/apps/as/src/pages/instl-sncn/ui/instl-view-page.tsx index d8f637f59..6e5bd3eca 100644 --- a/apps/as/src/pages/instl-sncn/ui/instl-view-page.tsx +++ b/apps/as/src/pages/instl-sncn/ui/instl-view-page.tsx @@ -6,7 +6,7 @@ import { useUserStore } from "@/shared/lib"; import { ElaprDocPrgrsStatus } from "@/entities/elapr"; import { useInstlSncnDetail } from "@/entities/instl-sncn"; import { useRvwMnpwMngLoginUserList } from "@/entities/rvw-mnpw"; -import { ProgressStatusSection, SupplementDemandSection } from "@/widgets/pbadms-sprt"; +import { AttachmentsViewSection, ProgressStatusSection, SupplementDemandSection } from "@/widgets/pbadms-sprt"; import { Skeleton } from "@common/components"; import { needsRoleUser01AtrzResolve } from "../lib/button-config"; @@ -15,7 +15,6 @@ import { EMPTY_CKLST_RESULT_LIST, useChecklistFormState } from "../lib/use-check import { PROGRESS_STATUS_COLUMNS } from "../model/columns"; import { FloatingActionButtons } from "./components/floating-action-buttons"; -import { AttachmentsSection } from "./sections/attachments-section"; import { BasicInfoSection } from "./sections/basic-info-section"; import { BusinessCostSection } from "./sections/business-cost-section"; import { BusinessScaleSection } from "./sections/business-scale-section"; @@ -107,7 +106,10 @@ function InstlViewPage({ id }: InstlViewPageProps) { formState={checklistFormState} setFormState={setChecklistFormState} /> - + - {esntlFileList.length > 0 && ( - -
- - - - 필수 자료명 - 파일명 - 크기 - - - - {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.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/sewer-bsc-plan/lib/form-data-mapper.ts b/apps/as/src/pages/sewer-bsc-plan/lib/form-data-mapper.ts index 6f84154d5..9c405f337 100644 --- a/apps/as/src/pages/sewer-bsc-plan/lib/form-data-mapper.ts +++ b/apps/as/src/pages/sewer-bsc-plan/lib/form-data-mapper.ts @@ -4,6 +4,7 @@ import type { SewerBscPlanDetResponseDto, } from "@/entities/sewer-bsc-plan"; import { sewerBscPlanApplySaveSchema } from "@/entities/sewer-bsc-plan"; +import { toEsntlAtchFileSaveList } from "@/widgets/pbadms-sprt"; import type { BizCstMatrixItem } from "./biz-cst-matrix-utils"; import { recalcFnrsCstSumAmt } from "./fnrs-cst-form-utils"; @@ -26,20 +27,6 @@ function mapSelectedValue(value?: string | null): "Y" | "N" | null { return null; } -function mapEsntlAtchFileList(esntlFileList: SewerBscPlanDetResponseDto["esntlFileList"]) { - const esntlMap = new Map(); - - for (const item of esntlFileList ?? []) { - if (!item.esntlAtchFileId || !item.atflCd) continue; - esntlMap.set(item.esntlAtchFileId, item.atflCd); - } - - return Array.from(esntlMap.entries()).map(([esntlAtchFileId, atchFileGroupId]) => ({ - esntlAtchFileId, - atchFileGroupId, - })); -} - function createEmptyTmplntDtlList(swtrmtFcltCd: string) { return TMPLNT_DTL_STEPS.map((planStpNo) => ({ planStpNo, @@ -218,7 +205,7 @@ export function mapDetailToFormData(data: SewerBscPlanDetResponseDto): SewerBscP selected: mapSelectedValue(item.rvwRsltAnmlyYn), picRvwCn: item.picRvwCn, })) ?? [], - esntlAtchFileList: mapEsntlAtchFileList(data.esntlFileList), + esntlAtchFileList: toEsntlAtchFileSaveList(data.esntlFileList), splmntDmndList: data.splmntDmndList?.map((item) => ({ splmntDmndSn: item.splmntDmndSn, diff --git a/apps/as/src/pages/sewer-bsc-plan/lib/payload-utils.ts b/apps/as/src/pages/sewer-bsc-plan/lib/payload-utils.ts index 320044a14..07224c8ef 100644 --- a/apps/as/src/pages/sewer-bsc-plan/lib/payload-utils.ts +++ b/apps/as/src/pages/sewer-bsc-plan/lib/payload-utils.ts @@ -1,11 +1,9 @@ import type { - EsntlAtchFileItem, - EsntlAtchFileMngTreeResponseDto, SewerBscPlanApplySaveRequestDto, SewerBscPlanDetResponseDto, SewerBscPlanSplmntDmndItem, } from "@/entities/sewer-bsc-plan"; -import type { SplmntDmndDialogConfirmPayload } from "@/widgets/pbadms-sprt"; +import { type SplmntDmndDialogConfirmPayload, toEsntlAtchFileSaveList } from "@/widgets/pbadms-sprt"; import type { ChecklistFormState, ReviewStepCode } from "../model/view-types"; @@ -13,22 +11,6 @@ import { toCklstSaveDto } from "./checklist-utils"; import { recalcFnrsCstSumAmt } from "./fnrs-cst-form-utils"; import { type ReviewOpinionGetFormDataResult, toRvwOpnnSaveDto } from "./review-opinion-utils"; -export function toEsntlAtchFileSaveDto( - esntlFileList: EsntlAtchFileMngTreeResponseDto[], -): EsntlAtchFileItem[] { - const esntlMap = new Map(); - - for (const item of esntlFileList) { - if (!item.esntlAtchFileId || !item.atflCd) continue; - esntlMap.set(item.esntlAtchFileId, item.atflCd); - } - - return Array.from(esntlMap.entries()).map(([esntlAtchFileId, atchFileGroupId]) => ({ - esntlAtchFileId, - atchFileGroupId, - })); -} - export function buildSavePayload( data: SewerBscPlanDetResponseDto, checklistFormState: ChecklistFormState, @@ -116,7 +98,7 @@ export function buildSavePayload( })), })), cklstResultList: toCklstSaveDto(cklstResultRows, checklistFormState, reviewStep), - esntlAtchFileList: toEsntlAtchFileSaveDto(esntlFileList), + esntlAtchFileList: toEsntlAtchFileSaveList(esntlFileList), }; } diff --git a/apps/as/src/pages/sewer-bsc-plan/ui/sections/attachments-section.tsx b/apps/as/src/pages/sewer-bsc-plan/ui/sections/attachments-section.tsx deleted file mode 100644 index e379d5097..000000000 --- a/apps/as/src/pages/sewer-bsc-plan/ui/sections/attachments-section.tsx +++ /dev/null @@ -1,127 +0,0 @@ -"use client"; - -import { Paperclip } from "lucide-react"; - -import { formatFileSize } from "@/shared/lib/file-utils"; -import { SectionCollapsible } from "@/shared/ui"; -import { useDownloadAttachmentByAtflCd } from "@/entities/attach"; -import type { - AtflMngClientResponseDto, - EsntlAtchFileMngTreeResponseDto, -} from "@/entities/sewer-bsc-plan"; -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.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.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/sewer-bsc-plan/ui/sewer-bsc-plan-view-page.tsx b/apps/as/src/pages/sewer-bsc-plan/ui/sewer-bsc-plan-view-page.tsx index e04483925..63b61052b 100644 --- a/apps/as/src/pages/sewer-bsc-plan/ui/sewer-bsc-plan-view-page.tsx +++ b/apps/as/src/pages/sewer-bsc-plan/ui/sewer-bsc-plan-view-page.tsx @@ -6,7 +6,7 @@ import { useUserStore } from "@/shared/lib"; import { ElaprDocPrgrsStatus } from "@/entities/elapr"; import { useRvwMnpwMngLoginUserList } from "@/entities/rvw-mnpw"; import { useSewerBscPlanDetails } from "@/entities/sewer-bsc-plan"; -import { ProgressStatusSection, SupplementDemandSection } from "@/widgets/pbadms-sprt"; +import { AttachmentsViewSection, ProgressStatusSection, SupplementDemandSection } from "@/widgets/pbadms-sprt"; import { Skeleton } from "@common/components"; import { needsRoleUser01AtrzResolve } from "../lib/button-config"; @@ -15,7 +15,6 @@ import { EMPTY_CKLST_RESULT_LIST, useChecklistFormState } from "../lib/use-check import { PROGRESS_STATUS_COLUMNS } from "../model/columns"; import { FloatingActionButtons } from "./components/floating-action-buttons"; -import { AttachmentsSection } from "./sections/attachments-section"; import { BasicInfoSection } from "./sections/basic-info-section"; import { BizCstMatrixSection } from "./sections/biz-cst-matrix-section"; import { ChecklistSection } from "./sections/checklist-section"; @@ -116,7 +115,7 @@ function SewerBscPlanViewPage({ id, propChgCycl }: SewerBscPlanViewPageProps) { formState={checklistFormState} setFormState={setChecklistFormState} /> - + (); + + for (const item of esntlFileList ?? []) { + const atchFileGroupId = getEsntlAtchFileGroupId(item); + if (!item.esntlAtchFileId || !atchFileGroupId) continue; + esntlMap.set(item.esntlAtchFileId, atchFileGroupId); + } + + return Array.from(esntlMap.entries()).map(([esntlAtchFileId, atchFileGroupId]) => ({ + esntlAtchFileId, + atchFileGroupId, + })); +} + export function isFormEsntlAtchComplete( essentialFiles: EsntlAtchFileMngTreeResponseDto[], esntlAtchFileList: EsntlAtchFileListItem[], diff --git a/apps/as/src/widgets/pbadms-sprt/ui/attachments-form-section.tsx b/apps/as/src/widgets/pbadms-sprt/ui/attachments-form-section.tsx index b4bce6b8a..c982b8eeb 100644 --- a/apps/as/src/widgets/pbadms-sprt/ui/attachments-form-section.tsx +++ b/apps/as/src/widgets/pbadms-sprt/ui/attachments-form-section.tsx @@ -4,6 +4,7 @@ import { useEffect, useRef, useState } from "react"; import { Controller, useFormContext } from "react-hook-form"; import { useQueryClient } from "@tanstack/react-query"; +import { Paperclip, X } from "lucide-react"; import { ENDPOINT } from "@/shared/config"; import { useTus } from "@/shared/lib"; @@ -30,7 +31,7 @@ import { type FieldPrefix, isEtcFileKey, toFileAccept, -} from "../lib/attachments-form-utils"; +} from "../lib/attachments-form-utils"; interface AttachmentsFormData { esntlAtchFileList: Array<{ @@ -103,22 +104,22 @@ function AttachmentsFormSection({ if (initialEsntlFiles?.length) { const nextUploadedFiles: Record = {}; - for (const file of initialEsntlFiles) { - if (!file.esntlAtchFileId || !file.atflCd || !file.atflId) continue; + for (const slot of initialEsntlFiles) { + if (!slot.esntlAtchFileId) continue; - const fileKey = `server::${file.esntlAtchFileId}::${file.atflId}`; - const fileName = file.orgnlFileNm ?? "첨부파일"; + const files = (slot.atchFileList ?? []).filter((file) => file.atflId && file.atflCd); + if (files.length === 0) continue; - nextUploadedFiles[file.esntlAtchFileId] = [ - ...(nextUploadedFiles[file.esntlAtchFileId] ?? []), - { - fileName, - fileKey, - atflId: file.atflId, - atflCd: file.atflCd, - }, - ]; - atflCdBySlotRef.current[file.esntlAtchFileId] = file.atflCd; + const [firstFile] = files; + if (!firstFile) continue; + + nextUploadedFiles[slot.esntlAtchFileId] = files.map((file) => ({ + fileName: file.orgnlFileNm ?? "첨부파일", + fileKey: `server::${slot.esntlAtchFileId}::${file.atflId}`, + atflId: file.atflId, + atflCd: file.atflCd, + })); + atflCdBySlotRef.current[slot.esntlAtchFileId] = firstFile.atflCd; } setUploadedFiles(nextUploadedFiles); @@ -372,7 +373,7 @@ function AttachmentsFormSection({ render={() => (

필수 첨부파일

- +
@@ -406,36 +407,43 @@ function AttachmentsFormSection({ {file.esntlAtchFileRmrk} - {uploadedFiles[file.esntlAtchFileId]?.length > 0 ? ( -
+ {uploadedFiles[file.esntlAtchFileId]?.length > 0 && ( +
{uploadedFiles[file.esntlAtchFileId].map((item, idx) => { const uploading = isUploading[item.fileKey] ?? false; return ( -
+
+ {item.file?.name ?? item.fileName} - {!uploading && ( - - )} +
); })}
- ) : ( + )} +
- )} +
)) @@ -475,35 +483,35 @@ function AttachmentsFormSection({ }} />
{etcFiles.length > 0 ? ( -
+
{etcFiles.map((item, idx) => { const uploading = isUploading[item.fileKey] ?? false; return (
- + + {item.file?.name ?? item.fileName} - + +
); })} diff --git a/apps/as/src/widgets/pbadms-sprt/ui/attachments-view-section.tsx b/apps/as/src/widgets/pbadms-sprt/ui/attachments-view-section.tsx new file mode 100644 index 000000000..4a49e5855 --- /dev/null +++ b/apps/as/src/widgets/pbadms-sprt/ui/attachments-view-section.tsx @@ -0,0 +1,123 @@ +"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 { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components"; + +interface AttachmentsViewSectionProps { + esntlFileList: EsntlAtchFileMngTreeResponseDto[]; + etcFileList: AtflMngClientResponseDto[]; +} + +function AttachmentsViewSection({ esntlFileList, etcFileList }: AttachmentsViewSectionProps) { + const { mutate: downloadByAtflCd } = useDownloadAttachmentByAtflCd(); + const { mutate: downloadByAtflId } = useDownloadAttachmentByAtflId(); + + return ( + <> + {esntlFileList.length > 0 && ( + +
+
+ + + 필수 자료명 + 파일명 + 크기 + + + + {esntlFileList.map((slot) => { + const files = slot.atchFileList ?? []; + + return ( + + + {slot.esntlAtchFileNm} + {slot.esntlAtchYn === "Y" ? ( + * + ) : null} + + + {files.length === 0 ? ( + 첨부없음 + ) : ( + + )} + + + {files.length === 0 ? ( + "-" + ) : ( +
+ {files.map((file) => ( +
{formatFileSize(file.atflFlieSz)}
+ ))} +
+ )} +
+
+ ); + })} +
+
+
+ + )} + + {etcFileList.length > 0 && ( + +
+ + + + 파일명 + 크기 + + + + {etcFileList.map((file) => ( + + + {file.atflCd ? ( + downloadByAtflCd(file.atflCd)} + > + + {file.orgnlFileNm || file.physFileNm || file.atflId} + + ) : ( + {file.orgnlFileNm || file.physFileNm || file.atflId} + )} + + + {formatFileSize(file.atflFlieSz)} + + + ))} + +
+
+
+ )} + + ); +} + +export { AttachmentsViewSection };