From cc3bc548c9995efc701eb8bafc116267f516e032 Mon Sep 17 00:00:00 2001 From: bicochan Date: Fri, 18 Sep 2026 16:27:51 +0900 Subject: [PATCH 1/3] =?UTF-8?q?[as]=20feat:=20=EC=9E=AC=EC=9B=90=ED=98=91?= =?UTF-8?q?=EC=9D=98=20=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../elapr/ui/elapr-apln-picker-grid.tsx | 7 +-- .../pages/fnrs-cnsltn/lib/button-config.ts | 27 +++++++--- .../ui/components/floating-action-buttons.tsx | 53 ++++++++++++++++--- .../components/pwrk-cnsltn-doc-mng-dialog.tsx | 4 +- .../pwrk-cnsltn-doc-pjtco-ajmt-table.tsx | 12 ++++- apps/as/src/shared/ui/uploaded-file-chip.tsx | 2 +- 6 files changed, 86 insertions(+), 19 deletions(-) diff --git a/apps/as/src/entities/elapr/ui/elapr-apln-picker-grid.tsx b/apps/as/src/entities/elapr/ui/elapr-apln-picker-grid.tsx index 7f7a88911..c8c84e1c6 100644 --- a/apps/as/src/entities/elapr/ui/elapr-apln-picker-grid.tsx +++ b/apps/as/src/entities/elapr/ui/elapr-apln-picker-grid.tsx @@ -90,6 +90,7 @@ function ElaprAplnPickerGrid({ ? isExternalLoading : aplnQuery.isPending && Boolean(elaprTmpltId); const pickingRow = pickingRowKey ? rows.find((r) => r.rowKey === pickingRowKey) : undefined; + const excludedVisibleRows = visibleRows.filter((r) => r.atrzSeComCd !== "ATRZ01"); function handleDragEnd(event: DragEndEvent) { const { active, over } = event; @@ -149,7 +150,7 @@ function ElaprAplnPickerGrid({ ) : !isReady ? (

결재서식을 선택하세요.

- ) : visibleRows.length === 0 ? ( + ) : excludedVisibleRows.length === 0 ? (

등록된 결재선이 없습니다. [결재선 추가]로 결재자를 등록하세요.

@@ -174,10 +175,10 @@ function ElaprAplnPickerGrid({ r.rowKey)} + items={excludedVisibleRows.map((r) => r.rowKey)} strategy={verticalListSortingStrategy} > - {visibleRows.map((row, index) => ( + {excludedVisibleRows.map((row, index) => ( { if (needsAtrzResolve && isRvwLoginUserLoading) return []; @@ -317,12 +319,11 @@ function FloatingActionButtons({ return; } - const isResultReply = prgrsSttsComCd === "RR"; const payload = buildTechReviewReplyPayload( data, checklistFormState, "ENV", - isResultReply ? "EP" : "RB", + "EP", "CP", getReviewOpinionFormData(), opnrNm, @@ -336,6 +337,36 @@ function FloatingActionButtons({ techReviewOpinionRef?.current?.resetForm(); } + async function handleRvwOpnnRplyClick() { + const { percent } = computeReviewStepChecklistProgress( + data.cklstResultList ?? [], + checklistFormState.results, + "ENV", + ); + + if (percent !== 100) { + alert("모든 체크리스트 항목을 체크해야 검토 의견 회신이 가능합니다."); + return; + } + + const payload = buildTechReviewReplyPayload( + data, + checklistFormState, + "ENV", + "RB", + "CP", + getReviewOpinionFormData(), + opnrNm, + ); + + await executeAction({ + action: FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwOpnnRply, + request: payload, + successMessage: "검토 의견 회신이 완료되었습니다.", + }); + techReviewOpinionRef?.current?.resetForm(); + } + async function handleRqstTechRvwClick() { if (prgrsSttsComCd === "LE") { const { percent } = computeReviewStepChecklistProgress( @@ -365,7 +396,7 @@ function FloatingActionButtons({ await executeAction({ action: FNRS_CNSLTN_VIEW_BUTTON_ACTION.rqstActnRvw, request: payload, - successMessage: "조치결과 검토의뢰가 완료되었습니다.", + successMessage: "조치 결과 검토 의뢰가 완료되었습니다.", }); } @@ -570,6 +601,11 @@ function FloatingActionButtons({ 결과 회신 )} + {shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwOpnnRply) && ( + + )} {shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwRply) && ( )} {shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.fileSplmnt) && ( @@ -698,7 +734,11 @@ function FloatingActionButtons({ fnrsCnsltnDocNo={docNo} atrzSeComCdList={atrzSeComCdList} prgrsSttsComCd={prgrsSttsComCd} - canSave={needsElaprPrgrsCheck && !hasPendingApprovalRequest ? false : undefined} + canSave={ + (needsElaprPrgrsCheck && !hasPendingApprovalRequest) || viewOnlyPwrkCnsltnDoc + ? false + : null + } onClose={() => setActiveDialog(null)} /> @@ -837,6 +877,7 @@ const buttonActionHandlers: Record = rjct: handleChangePrgrsSttsComCd, sncnAprv: handleChangePrgrsSttsComCd, rvwRply: handleTechReview, + rvwOpnnRply: handleTechReview, rqstTechRvw: handleRequestExamine, fileSplmnt: handleDmndSplmnt, dsgntRvwr: handleDsgntRvwr, diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/components/pwrk-cnsltn-doc-mng-dialog.tsx b/apps/as/src/pages/fnrs-cnsltn/ui/components/pwrk-cnsltn-doc-mng-dialog.tsx index 040d3ec40..a740f7356 100644 --- a/apps/as/src/pages/fnrs-cnsltn/ui/components/pwrk-cnsltn-doc-mng-dialog.tsx +++ b/apps/as/src/pages/fnrs-cnsltn/ui/components/pwrk-cnsltn-doc-mng-dialog.tsx @@ -43,14 +43,14 @@ const PWRK_ATCH_FILE_KEY_PREFIX = "pwrk-cnsltn-doc::"; const APPROVER_ATRZ_SE_COM_CDS = new Set(["ATRZ03", "ATRZ99"]); -const VIEW_ONLY_PRGRS_STTS_COM_CDS = new Set(["RA", "RR", "EP", "TA"]); +const VIEW_ONLY_PRGRS_STTS_COM_CDS = new Set(["TP", "RA", "RR", "EP", "TA"]); interface PwrkCnsltnDocMngDialogProps { open: boolean; fnrsCnsltnDocNo: string; atrzSeComCdList?: string[]; prgrsSttsComCd?: string; - canSave?: boolean; + canSave?: boolean | null; onClose: () => void; } diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/components/pwrk-cnsltn-doc-pjtco-ajmt-table.tsx b/apps/as/src/pages/fnrs-cnsltn/ui/components/pwrk-cnsltn-doc-pjtco-ajmt-table.tsx index cc0c9ba35..bd1cf6120 100644 --- a/apps/as/src/pages/fnrs-cnsltn/ui/components/pwrk-cnsltn-doc-pjtco-ajmt-table.tsx +++ b/apps/as/src/pages/fnrs-cnsltn/ui/components/pwrk-cnsltn-doc-pjtco-ajmt-table.tsx @@ -43,11 +43,21 @@ function PwrkCnsltnDocPjtcoAjmtTable({ return ( + + + + + 구분 검증결과 - 검증금액 + +
+ 검증금액 + (단위: 백만 원) +
+
diff --git a/apps/as/src/shared/ui/uploaded-file-chip.tsx b/apps/as/src/shared/ui/uploaded-file-chip.tsx index a7eb6fa22..82be5d581 100644 --- a/apps/as/src/shared/ui/uploaded-file-chip.tsx +++ b/apps/as/src/shared/ui/uploaded-file-chip.tsx @@ -12,7 +12,7 @@ interface UploadedFileChipProps { function UploadedFileChip({ fileName, isUploading = false, onRemove }: UploadedFileChipProps) { return ( -
+
{fileName} From f40d6fd5e862fdeb44a7951ab99746bcfc63310c Mon Sep 17 00:00:00 2001 From: bicochan Date: Fri, 18 Sep 2026 16:51:00 +0900 Subject: [PATCH 2/3] =?UTF-8?q?[as]=20feat:=20AttachmentDownloadLink=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?=EB=B0=8F=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/as/src/entities/attach/index.ts | 4 ++ .../attach/ui/attachment-download-link.tsx | 54 +++++++++++++++++++ .../sections/tech-review-opinion-section.tsx | 18 ++----- .../sections/tech-review-opinion-section.tsx | 18 ++----- .../sections/tech-review-opinion-section.tsx | 18 ++----- .../sections/tech-review-opinion-section.tsx | 18 ++----- .../ui/sections/tmplnt-section.tsx | 17 +++--- .../ui/attachments-view-section.tsx | 31 ++++------- .../ui/splmnt-dmnd-form-section.tsx | 12 ++--- .../ui/supplement-demand-section.tsx | 28 ++++------ 10 files changed, 109 insertions(+), 109 deletions(-) create mode 100644 apps/as/src/entities/attach/ui/attachment-download-link.tsx diff --git a/apps/as/src/entities/attach/index.ts b/apps/as/src/entities/attach/index.ts index ca804b4a0..69b220102 100644 --- a/apps/as/src/entities/attach/index.ts +++ b/apps/as/src/entities/attach/index.ts @@ -6,3 +6,7 @@ export { } from "./api/queries"; export { parseFilenameFromResponse } from "./lib/parse-filename-from-response"; export type { Attach, AttachService } from "./model/attach-interface"; +export { + AttachmentDownloadLink, + type AttachmentDownloadLinkProps, +} from "./ui/attachment-download-link"; diff --git a/apps/as/src/entities/attach/ui/attachment-download-link.tsx b/apps/as/src/entities/attach/ui/attachment-download-link.tsx new file mode 100644 index 000000000..85aab6ec5 --- /dev/null +++ b/apps/as/src/entities/attach/ui/attachment-download-link.tsx @@ -0,0 +1,54 @@ +"use client"; + +import { Paperclip } from "lucide-react"; + +import { cn } from "@common/components"; + +import { useDownloadAttachmentByAtflCd, useDownloadAttachmentByAtflId } from "../api/queries"; + +interface AttachmentDownloadLinkProps { + atflCd?: string; + atflId?: string; + fileName?: string; + className?: string; +} + +function AttachmentDownloadLink({ + atflCd, + atflId, + fileName, + className, +}: AttachmentDownloadLinkProps) { + const downloadByAtflCd = useDownloadAttachmentByAtflCd(); + const downloadByAtflId = useDownloadAttachmentByAtflId(); + const displayName = fileName || "첨부파일"; + const canDownload = Boolean(atflId || atflCd); + const isPending = atflId ? downloadByAtflId.isPending : downloadByAtflCd.isPending; + + function handleClick() { + if (atflId) { + downloadByAtflId.mutate(atflId); + return; + } + if (atflCd) downloadByAtflCd.mutate(atflCd); + } + + return ( + + ); +} + +export { AttachmentDownloadLink }; +export type { AttachmentDownloadLinkProps }; 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 index d467bdb31..f81f0aaf9 100644 --- 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 @@ -2,14 +2,11 @@ import { forwardRef, useEffect, useImperativeHandle, useState } from "react"; -import { Paperclip } from "lucide-react"; - -import { PROXY_HOST } from "@/shared/config"; import { dayjs, getDefaultFileKey, useTus } from "@/shared/lib"; import { TextShimmer } from "@/shared/ui"; import { SectionCollapsible } from "@/shared/ui"; import { Field, FieldLabel, FieldSet } from "@/shared/ui/field"; -import { attachService } from "@/entities/attach"; +import { AttachmentDownloadLink, attachService } from "@/entities/attach"; import type { DesignCnstnRvwOpnnResponseDto } from "@/entities/design-cnstn"; import { Button, @@ -319,15 +316,10 @@ function TechReviewOpinionTable({ items }: TechReviewOpinionTableProps) { {item.atchFileInfo?.atflCd ? ( - - - {item.atchFileInfo.orgnlFileNm || "다운로드"} - + ) : ( "-" )} diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/sections/tech-review-opinion-section.tsx b/apps/as/src/pages/fnrs-cnsltn/ui/sections/tech-review-opinion-section.tsx index 837a97239..a4c41d527 100644 --- a/apps/as/src/pages/fnrs-cnsltn/ui/sections/tech-review-opinion-section.tsx +++ b/apps/as/src/pages/fnrs-cnsltn/ui/sections/tech-review-opinion-section.tsx @@ -2,13 +2,10 @@ import { forwardRef, useEffect, useImperativeHandle, useState } from "react"; -import { Paperclip } from "lucide-react"; - -import { PROXY_HOST } from "@/shared/config"; import { dayjs, getDefaultFileKey, useTus } from "@/shared/lib"; import { AttachmentFileField, SectionCollapsible } from "@/shared/ui"; import { Field, FieldLabel, FieldSet } from "@/shared/ui/field"; -import { attachService } from "@/entities/attach"; +import { AttachmentDownloadLink, attachService } from "@/entities/attach"; import type { FnrsCnsltnRvwOpnnResponseDto } from "@/entities/fnrs-cnsltn"; import { RadioGroup, @@ -320,15 +317,10 @@ function TechReviewOpinionTable({ items }: TechReviewOpinionTableProps) { {item.atchFileInfo?.atflCd ? ( - - - {item.atchFileInfo.orgnlFileNm || "다운로드"} - + ) : ( "-" )} diff --git a/apps/as/src/pages/instl-sncn/ui/sections/tech-review-opinion-section.tsx b/apps/as/src/pages/instl-sncn/ui/sections/tech-review-opinion-section.tsx index ba787fd61..316c62d96 100644 --- a/apps/as/src/pages/instl-sncn/ui/sections/tech-review-opinion-section.tsx +++ b/apps/as/src/pages/instl-sncn/ui/sections/tech-review-opinion-section.tsx @@ -2,14 +2,11 @@ import { forwardRef, useEffect, useImperativeHandle, useState } from "react"; -import { Paperclip } from "lucide-react"; - -import { PROXY_HOST } from "@/shared/config"; import { dayjs, getDefaultFileKey, useTus } from "@/shared/lib"; import { TextShimmer } from "@/shared/ui"; import { SectionCollapsible } from "@/shared/ui"; import { Field, FieldLabel, FieldSet } from "@/shared/ui/field"; -import { attachService } from "@/entities/attach"; +import { AttachmentDownloadLink, attachService } from "@/entities/attach"; import type { InstlSncnRvwOpnnResponseDto, ReviewStepCode } from "@/entities/instl-sncn"; import { Button, @@ -331,15 +328,10 @@ function TechReviewOpinionTable({ items }: TechReviewOpinionTableProps) { {item.atchFileInfo?.atflCd ? ( - - - {item.atchFileInfo.orgnlFileNm || "다운로드"} - + ) : ( "-" )} diff --git a/apps/as/src/pages/sewer-bsc-plan/ui/sections/tech-review-opinion-section.tsx b/apps/as/src/pages/sewer-bsc-plan/ui/sections/tech-review-opinion-section.tsx index 1781f183e..f7371c342 100644 --- a/apps/as/src/pages/sewer-bsc-plan/ui/sections/tech-review-opinion-section.tsx +++ b/apps/as/src/pages/sewer-bsc-plan/ui/sections/tech-review-opinion-section.tsx @@ -2,14 +2,11 @@ import { forwardRef, useEffect, useImperativeHandle, useState } from "react"; -import { Paperclip } from "lucide-react"; - -import { PROXY_HOST } from "@/shared/config"; import { dayjs, getDefaultFileKey, useTus } from "@/shared/lib"; import { TextShimmer } from "@/shared/ui"; import { SectionCollapsible } from "@/shared/ui"; import { Field, FieldLabel, FieldSet } from "@/shared/ui/field"; -import { attachService } from "@/entities/attach"; +import { AttachmentDownloadLink, attachService } from "@/entities/attach"; import type { SewerBscPlanRvwOpnnResponseDto } from "@/entities/sewer-bsc-plan"; import { Button, @@ -338,15 +335,10 @@ function TechReviewOpinionTable({ items }: TechReviewOpinionTableProps) { {item.atchFileInfo?.atflCd ? ( - - - {item.atchFileInfo.orgnlFileNm || "다운로드"} - + ) : ( "-" )} diff --git a/apps/as/src/pages/sewer-bsc-plan/ui/sections/tmplnt-section.tsx b/apps/as/src/pages/sewer-bsc-plan/ui/sections/tmplnt-section.tsx index 7f87254d3..a2b4f8fa5 100644 --- a/apps/as/src/pages/sewer-bsc-plan/ui/sections/tmplnt-section.tsx +++ b/apps/as/src/pages/sewer-bsc-plan/ui/sections/tmplnt-section.tsx @@ -2,10 +2,8 @@ import { useMemo, useState } from "react"; -import { Paperclip } from "lucide-react"; - import { SectionCollapsible } from "@/shared/ui"; -import { useDownloadAttachmentByAtflCd } from "@/entities/attach"; +import { AttachmentDownloadLink } from "@/entities/attach"; import { useFacilityList } from "@/entities/bns-if"; import { useNowDmgMtncGnsvy } from "@/entities/dc"; import type { @@ -176,7 +174,6 @@ function TmplntFacilityMatrix({ } function TmplntSection({ tmplntList, det, dtl }: TmplntSectionProps) { - const { mutate: downloadAttachment } = useDownloadAttachmentByAtflCd(); const [selectedCode, setSelectedCode] = useState(tmplntList[0]?.swtrmtFcltCd ?? ""); const { data: facilityListData } = useFacilityList( { sido: det.sido ?? "", gugun: det.gugun ?? "" }, @@ -261,13 +258,11 @@ function TmplntSection({ tmplntList, det, dtl }: TmplntSectionProps) { {facilityName} 계획평면도 {item.atchFileGroupId ? ( - downloadAttachment(item.atchFileGroupId ?? "")} - > - - {item.atchFileInfo?.orgnlFileNm || "다운로드"} - + ) : ( "-" )} 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 index 4a49e5855..2a492fd56 100644 --- a/apps/as/src/widgets/pbadms-sprt/ui/attachments-view-section.tsx +++ b/apps/as/src/widgets/pbadms-sprt/ui/attachments-view-section.tsx @@ -1,11 +1,9 @@ "use client"; -import { Paperclip } from "lucide-react"; - import { formatFileSize } from "@/shared/lib/file-utils"; import type { AtflMngClientResponseDto, EsntlAtchFileMngTreeResponseDto } from "@/shared/model"; import { SectionCollapsible } from "@/shared/ui"; -import { useDownloadAttachmentByAtflCd, useDownloadAttachmentByAtflId } from "@/entities/attach"; +import { AttachmentDownloadLink } from "@/entities/attach"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components"; interface AttachmentsViewSectionProps { @@ -14,9 +12,6 @@ interface AttachmentsViewSectionProps { } function AttachmentsViewSection({ esntlFileList, etcFileList }: AttachmentsViewSectionProps) { - const { mutate: downloadByAtflCd } = useDownloadAttachmentByAtflCd(); - const { mutate: downloadByAtflId } = useDownloadAttachmentByAtflId(); - return ( <> {esntlFileList.length > 0 && ( @@ -48,14 +43,12 @@ function AttachmentsViewSection({ esntlFileList, etcFileList }: AttachmentsViewS ) : (
{files.map((file) => ( - downloadByAtflId(file.atflId)} - > - - {file.orgnlFileNm || "다운로드"} - + atflId={file.atflId} + fileName={file.orgnlFileNm || "다운로드"} + className="items-center" + /> ))}
)} @@ -95,13 +88,11 @@ function AttachmentsViewSection({ esntlFileList, etcFileList }: AttachmentsViewS {file.atflCd ? ( - downloadByAtflCd(file.atflCd)} - > - - {file.orgnlFileNm || file.physFileNm || file.atflId} - + ) : ( {file.orgnlFileNm || file.physFileNm || file.atflId} )} diff --git a/apps/as/src/widgets/pbadms-sprt/ui/splmnt-dmnd-form-section.tsx b/apps/as/src/widgets/pbadms-sprt/ui/splmnt-dmnd-form-section.tsx index eea7fb7af..348cfe52e 100644 --- a/apps/as/src/widgets/pbadms-sprt/ui/splmnt-dmnd-form-section.tsx +++ b/apps/as/src/widgets/pbadms-sprt/ui/splmnt-dmnd-form-section.tsx @@ -3,11 +3,9 @@ import { useEffect, useId, useRef, useState } from "react"; import { Controller, useFormContext, useWatch } from "react-hook-form"; -import { Paperclip } from "lucide-react"; - import { dayjs, getDefaultFileKey, useTus } from "@/shared/lib"; import { AttachmentFileField, SectionCollapsible } from "@/shared/ui"; -import { attachService } from "@/entities/attach"; +import { AttachmentDownloadLink, attachService } from "@/entities/attach"; import { Badge, Table, @@ -274,10 +272,10 @@ function SplmntDmndFormSection({ readOnly = false }: SplmntDmndFormSectionProps)
{item.splmntDmndCn || "-"}
{item.splmntDmndAtchFileGroupId ? ( - - - {item.splmntDmndAtchFileNm || "첨부파일"} - + ) : null}
diff --git a/apps/as/src/widgets/pbadms-sprt/ui/supplement-demand-section.tsx b/apps/as/src/widgets/pbadms-sprt/ui/supplement-demand-section.tsx index 1da222fdf..7151323bb 100644 --- a/apps/as/src/widgets/pbadms-sprt/ui/supplement-demand-section.tsx +++ b/apps/as/src/widgets/pbadms-sprt/ui/supplement-demand-section.tsx @@ -1,11 +1,9 @@ "use client"; -import { Paperclip } from "lucide-react"; - import { dayjs } from "@/shared/lib"; import type { SplmntDmndResponseExtension, SplmntDmndSaveBase } from "@/shared/model"; import { SectionCollapsible } from "@/shared/ui"; -import { useDownloadAttachmentByAtflCd } from "@/entities/attach"; +import { AttachmentDownloadLink } from "@/entities/attach"; import { Badge, Table, @@ -23,8 +21,6 @@ interface SupplementDemandSectionProps { } function SupplementDemandSection({ splmntDmndList }: SupplementDemandSectionProps) { - const { mutate: downloadAttachment } = useDownloadAttachmentByAtflCd(); - if (splmntDmndList.length === 0) { return null; } @@ -70,13 +66,10 @@ function SupplementDemandSection({ splmntDmndList }: SupplementDemandSectionProp
{item.splmntDmndCn || "-"}
{item.splmntDmndAtchFileGroupId ? ( - downloadAttachment(item.splmntDmndAtchFileGroupId!)} - > - - {dmndAtchFileNm || "다운로드"} - + ) : null}
@@ -89,13 +82,10 @@ function SupplementDemandSection({ splmntDmndList }: SupplementDemandSectionProp
{item.splmntCn || "-"}
{item.splmntAtchFileGroupId ? ( - downloadAttachment(item.splmntAtchFileGroupId!)} - > - - {cmptnAtchFileNm || "다운로드"} - + ) : null}
From 110d6c689c68c62812be1d33a7e89811b52226ff Mon Sep 17 00:00:00 2001 From: bicochan Date: Fri, 18 Sep 2026 17:33:45 +0900 Subject: [PATCH 3/3] =?UTF-8?q?[as]=20feat:=20UploadedFileChip=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20props=20=EC=B6=94=EA=B0=80(onDown?= =?UTF-8?q?load)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/esntl-atch-file-form-dialog.tsx | 6 +++- .../ui/components/floating-action-buttons.tsx | 2 +- .../components/pwrk-cnsltn-doc-mng-dialog.tsx | 6 +++- .../pages/fnrs-cnsltn/ui/fnrs-form-page.tsx | 36 ++++++++++--------- .../sections/tech-review-opinion-section.tsx | 17 +++++++-- .../src/shared/ui/attachment-file-field.tsx | 2 ++ apps/as/src/shared/ui/uploaded-file-chip.tsx | 29 ++++++++++++--- .../ui/attachments-form-section.tsx | 9 ++++- .../pbadms-sprt/ui/splmnt-dmnd-dialog.tsx | 6 +++- .../ui/splmnt-dmnd-form-section.tsx | 6 +++- 10 files changed, 90 insertions(+), 29 deletions(-) diff --git a/apps/as/src/pages/esntl-atch-file-mng/ui/esntl-atch-file-form-dialog.tsx b/apps/as/src/pages/esntl-atch-file-mng/ui/esntl-atch-file-form-dialog.tsx index 710d6c93d..c7c63ffed 100644 --- a/apps/as/src/pages/esntl-atch-file-mng/ui/esntl-atch-file-form-dialog.tsx +++ b/apps/as/src/pages/esntl-atch-file-mng/ui/esntl-atch-file-form-dialog.tsx @@ -5,7 +5,7 @@ import { useForm } from "react-hook-form"; import { getDefaultFileKey, useTus } from "@/shared/lib"; import { AttachmentFileField } from "@/shared/ui"; -import { attachService } from "@/entities/attach"; +import { attachService, useDownloadAttachmentByAtflId } from "@/entities/attach"; import { CodeSelect } from "@/entities/code"; import type { EsntlAtchFileMng } from "@/entities/esntl"; import { @@ -61,6 +61,7 @@ function EsntlAtchFileFormDialog({ const formId = useId(); const atchFileGroupIdRef = useRef(""); const [uploadedFiles, setUploadedFiles] = useState([]); + const { mutate: downloadByAtflId } = useDownloadAttachmentByAtflId(); const { register, handleSubmit, reset, setValue, watch } = useForm({ defaultValues: { @@ -219,6 +220,9 @@ function EsntlAtchFileFormDialog({ files={uploadedFiles.map((item) => ({ fileKey: item.fileKey, fileName: item.fileName, + onDownload: item.atflId + ? () => downloadByAtflId(item.atflId!) + : undefined, }))} inputId={fileInputId} isUploadingByKey={isUploading} diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/components/floating-action-buttons.tsx b/apps/as/src/pages/fnrs-cnsltn/ui/components/floating-action-buttons.tsx index 2c32522cc..5d6d58371 100644 --- a/apps/as/src/pages/fnrs-cnsltn/ui/components/floating-action-buttons.tsx +++ b/apps/as/src/pages/fnrs-cnsltn/ui/components/floating-action-buttons.tsx @@ -597,7 +597,7 @@ function FloatingActionButtons({ )} {shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.sncnAprv) && ( - )} diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/components/pwrk-cnsltn-doc-mng-dialog.tsx b/apps/as/src/pages/fnrs-cnsltn/ui/components/pwrk-cnsltn-doc-mng-dialog.tsx index a740f7356..97a7a9e9f 100644 --- a/apps/as/src/pages/fnrs-cnsltn/ui/components/pwrk-cnsltn-doc-mng-dialog.tsx +++ b/apps/as/src/pages/fnrs-cnsltn/ui/components/pwrk-cnsltn-doc-mng-dialog.tsx @@ -8,7 +8,7 @@ import { PanelRightClose, PanelRightOpen } from "lucide-react"; import { getDefaultFileKey, useAlertStore, useTus } from "@/shared/lib"; import { AttachmentFileField } from "@/shared/ui"; -import { attachService, parseFilenameFromResponse } from "@/entities/attach"; +import { attachService, parseFilenameFromResponse, useDownloadAttachmentByAtflId } from "@/entities/attach"; import { pwrkCnsltnDocCrtRsltSaveRequestSchema, pwrkCnsltnDocCrtRsltService, @@ -78,6 +78,7 @@ function PwrkCnsltnDocMngDialog({ const [atchFileGroupId, setAtchFileGroupId] = useState(""); const [uploadedFiles, setUploadedFiles] = useState([]); const [isPjtcoAjmtTableOpen, setIsPjtcoAjmtTableOpen] = useState(false); + const { mutate: downloadByAtflId } = useDownloadAttachmentByAtflId(); const { data: pwrkCnsltnData, @@ -344,6 +345,9 @@ function PwrkCnsltnDocMngDialog({ files={uploadedFiles.map((item) => ({ fileKey: item.fileKey, fileName: item.fileName, + onDownload: item.atflId + ? () => downloadByAtflId(item.atflId!) + : undefined, }))} inputId="pwrk-cnsltn-doc-atch-file-input" isUploadingByKey={isUploading} diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/fnrs-form-page.tsx b/apps/as/src/pages/fnrs-cnsltn/ui/fnrs-form-page.tsx index 632c51e3b..cac88ea86 100644 --- a/apps/as/src/pages/fnrs-cnsltn/ui/fnrs-form-page.tsx +++ b/apps/as/src/pages/fnrs-cnsltn/ui/fnrs-form-page.tsx @@ -332,24 +332,26 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps) )} ) : ( - + <> + {fnrsCnsltnDocNo ? ( + + ) : null} + + )} - {isSplmntPrgrsStts && fnrsCnsltnDocNo ? ( - - ) : null}
diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/sections/tech-review-opinion-section.tsx b/apps/as/src/pages/fnrs-cnsltn/ui/sections/tech-review-opinion-section.tsx index a4c41d527..b00dfce22 100644 --- a/apps/as/src/pages/fnrs-cnsltn/ui/sections/tech-review-opinion-section.tsx +++ b/apps/as/src/pages/fnrs-cnsltn/ui/sections/tech-review-opinion-section.tsx @@ -5,7 +5,11 @@ import { forwardRef, useEffect, useImperativeHandle, useState } from "react"; import { dayjs, getDefaultFileKey, useTus } from "@/shared/lib"; import { AttachmentFileField, SectionCollapsible } from "@/shared/ui"; import { Field, FieldLabel, FieldSet } from "@/shared/ui/field"; -import { AttachmentDownloadLink, attachService } from "@/entities/attach"; +import { + AttachmentDownloadLink, + attachService, + useDownloadAttachmentByAtflId, +} from "@/entities/attach"; import type { FnrsCnsltnRvwOpnnResponseDto } from "@/entities/fnrs-cnsltn"; import { RadioGroup, @@ -79,6 +83,7 @@ const TechReviewOpinionSection = forwardRef< opnrSeComCd: editableReviewStep ?? "", }); const [uploadedFile, setUploadedFile] = useState(null); + const { mutate: downloadByAtflId } = useDownloadAttachmentByAtflId(); useEffect(() => { setFormData((prev) => ({ @@ -243,7 +248,15 @@ const TechReviewOpinionSection = forwardRef< downloadByAtflId(uploadedFile.atflId!) + : undefined, + }, + ] : [] } inputId={`rvw-opnn-${reviewStep}-file-input`} diff --git a/apps/as/src/shared/ui/attachment-file-field.tsx b/apps/as/src/shared/ui/attachment-file-field.tsx index 6867a8323..8eac0dce6 100644 --- a/apps/as/src/shared/ui/attachment-file-field.tsx +++ b/apps/as/src/shared/ui/attachment-file-field.tsx @@ -6,6 +6,7 @@ import { UploadedFileChip } from "./uploaded-file-chip"; interface AttachmentFileFieldItem { fileKey: string; fileName: string; + onDownload?: () => void; } interface AttachmentFileFieldProps { @@ -41,6 +42,7 @@ function AttachmentFileField({ fileName={file.fileName} isUploading={isUploadingByKey[file.fileKey] ?? false} onRemove={onRemove ? () => onRemove(index) : undefined} + onDownload={file.onDownload} /> ))} diff --git a/apps/as/src/shared/ui/uploaded-file-chip.tsx b/apps/as/src/shared/ui/uploaded-file-chip.tsx index 82be5d581..2fb4b437a 100644 --- a/apps/as/src/shared/ui/uploaded-file-chip.tsx +++ b/apps/as/src/shared/ui/uploaded-file-chip.tsx @@ -8,15 +8,36 @@ interface UploadedFileChipProps { fileName: string; isUploading?: boolean; onRemove?: () => void; + onDownload?: () => void; } -function UploadedFileChip({ fileName, isUploading = false, onRemove }: UploadedFileChipProps) { +function UploadedFileChip({ + fileName, + isUploading = false, + onRemove, + onDownload, +}: UploadedFileChipProps) { + const canDownload = Boolean(onDownload) && !isUploading; + return (
- - {fileName} - + {canDownload ? ( + + ) : ( + + {fileName} + + )} {onRemove ? (
diff --git a/apps/as/src/widgets/pbadms-sprt/ui/splmnt-dmnd-dialog.tsx b/apps/as/src/widgets/pbadms-sprt/ui/splmnt-dmnd-dialog.tsx index 2e225a286..8e1facf38 100644 --- a/apps/as/src/widgets/pbadms-sprt/ui/splmnt-dmnd-dialog.tsx +++ b/apps/as/src/widgets/pbadms-sprt/ui/splmnt-dmnd-dialog.tsx @@ -4,7 +4,7 @@ import { useEffect, useId, useState } from "react"; import { getDefaultFileKey, useTus } from "@/shared/lib"; import { AttachmentFileField } from "@/shared/ui"; -import { attachService } from "@/entities/attach"; +import { attachService, useDownloadAttachmentByAtflId } from "@/entities/attach"; import { CodeSelect } from "@/entities/code"; import { Button, @@ -96,6 +96,7 @@ function SplmntDmndDialog({ }: SplmntDmndDialogProps) { const formId = useId(); const [rows, setRows] = useState([createEmptyRow()]); + const { mutate: downloadByAtflId } = useDownloadAttachmentByAtflId(); const { uploadFileAsync, isUploading, uploads } = useTus({ onSuccess: (atflCd, atflId, _file, fileKey) => { @@ -289,6 +290,9 @@ function SplmntDmndDialog({ files={row.uploadedFiles.map((file) => ({ fileKey: file.fileKey, fileName: file.fileName, + onDownload: file.atflId + ? () => downloadByAtflId(file.atflId!) + : undefined, }))} inputId={fileInputId} isUploadingByKey={isUploading} diff --git a/apps/as/src/widgets/pbadms-sprt/ui/splmnt-dmnd-form-section.tsx b/apps/as/src/widgets/pbadms-sprt/ui/splmnt-dmnd-form-section.tsx index 348cfe52e..8fdbec031 100644 --- a/apps/as/src/widgets/pbadms-sprt/ui/splmnt-dmnd-form-section.tsx +++ b/apps/as/src/widgets/pbadms-sprt/ui/splmnt-dmnd-form-section.tsx @@ -5,7 +5,7 @@ import { Controller, useFormContext, useWatch } from "react-hook-form"; import { dayjs, getDefaultFileKey, useTus } from "@/shared/lib"; import { AttachmentFileField, SectionCollapsible } from "@/shared/ui"; -import { AttachmentDownloadLink, attachService } from "@/entities/attach"; +import { AttachmentDownloadLink, attachService, useDownloadAttachmentByAtflId } from "@/entities/attach"; import { Badge, Table, @@ -73,6 +73,7 @@ function SplmntDmndFormSection({ readOnly = false }: SplmntDmndFormSectionProps) const initialFilesMappedRef = useRef(false); const isMountedRef = useRef(true); const atflCdByRowRef = useRef>({}); + const { mutate: downloadByAtflId } = useDownloadAttachmentByAtflId(); const { uploadFileAsync, isUploading, uploads } = useTus({ onSuccess: (atflCd, atflId, _file, fileKey) => { @@ -306,6 +307,9 @@ function SplmntDmndFormSection({ readOnly = false }: SplmntDmndFormSectionProps) files={rowFiles.map((file) => ({ fileKey: file.fileKey, fileName: file.fileName, + onDownload: file.atflId + ? () => downloadByAtflId(file.atflId!) + : undefined, }))} inputId={fileInputId} isUploadingByKey={isUploading}