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 987c1d6ab..064691f14 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 @@ -1,7 +1,7 @@ "use client"; import { useEffect, useRef, useState } from "react"; -import { Controller, useFormContext } from "react-hook-form"; +import { useFormContext } from "react-hook-form"; import { useQueryClient } from "@tanstack/react-query"; import { Download } from "lucide-react"; @@ -78,7 +78,7 @@ function AttachmentsFormSection({ initialEsntlFiles, initialEtcFiles, }: AttachmentsFormSectionProps) { - const { control, getValues, setValue } = useFormContext(); + const { getValues, setValue } = useFormContext(); const queryClient = useQueryClient(); const [uploadedFiles, setUploadedFiles] = useState>({}); const [etcFiles, setEtcFiles] = useState([]); @@ -373,89 +373,83 @@ function AttachmentsFormSection({ return (
- ( -
-

필수 첨부파일

- - - - - - - - - - 파일명 - - 파일 확장자 - 파일첨부 - - - - {essentialFiles.length === 0 ? ( - - - 조회된 필수 첨부파일 항목이 없습니다. +
+

필수 첨부파일

+
+ + + + + + + + + 파일명 + + 파일 확장자 + 파일첨부 + + + + {essentialFiles.length === 0 ? ( + + + 조회된 필수 첨부파일 항목이 없습니다. + + + ) : ( + essentialFiles.map((file) => { + const atflCd = file.atchFileGroupId ?? ""; + + return ( + + +
+ {file.esntlAtchFileNm} + {atflCd ? ( + + ) : null} +
+
+ {file.esntlAtchFileRmrk} + + ({ + fileKey: item.fileKey, + fileName: item.file?.name ?? item.fileName, + onDownload: item.atflId + ? () => downloadByAtflId(item.atflId!) + : undefined, + }))} + inputId={`${file.esntlAtchFileId}-file-input`} + isUploadingByKey={isUploading} + accept={toFileAccept(file.esntlAtchFileRmrk) || undefined} + onAdd={(fileList) => + void handleEssentialFileUpload(file.esntlAtchFileId, fileList) + } + onRemove={(index) => + void removeEssentialFile(file.esntlAtchFileId, index) + } + />
- ) : ( - essentialFiles.map((file) => { - const atflCd = file.atchFileGroupId ?? ""; - - return ( - - -
- {file.esntlAtchFileNm} - {atflCd ? ( - - ) : null} -
-
- {file.esntlAtchFileRmrk} - - ({ - fileKey: item.fileKey, - fileName: item.file?.name ?? item.fileName, - onDownload: item.atflId - ? () => downloadByAtflId(item.atflId!) - : undefined, - }))} - inputId={`${file.esntlAtchFileId}-file-input`} - isUploadingByKey={isUploading} - accept={toFileAccept(file.esntlAtchFileRmrk) || undefined} - onAdd={(fileList) => - void handleEssentialFileUpload(file.esntlAtchFileId, fileList) - } - onRemove={(index) => - void removeEssentialFile(file.esntlAtchFileId, index) - } - /> - -
- ); - }) - )} -
-
-
- )} - /> + ); + }) + )} + + +
diff --git a/apps/as/src/widgets/pbadms-sprt/ui/business-cost-form-section.tsx b/apps/as/src/widgets/pbadms-sprt/ui/business-cost-form-section.tsx index dc50504a5..9bc5ce92d 100644 --- a/apps/as/src/widgets/pbadms-sprt/ui/business-cost-form-section.tsx +++ b/apps/as/src/widgets/pbadms-sprt/ui/business-cost-form-section.tsx @@ -1,7 +1,7 @@ "use client"; import { useEffect, useMemo } from "react"; -import { Controller, useFormContext, useWatch } from "react-hook-form"; +import { useFormContext, useWatch } from "react-hook-form"; import { SectionCollapsible } from "@/shared/ui"; import { Field } from "@/shared/ui/field"; @@ -31,6 +31,16 @@ interface BusinessCostFormData { type CostFieldKey = "bscPlanAmt" | "bgtRfltAmt" | "tstmDesignAmt"; +function isSameBizCstCodes( + current: BusinessCostFormData["bizCstList"], + next: BusinessCostFormData["bizCstList"], +) { + return ( + current.length === next.length && + current.every((item, index) => item.acntgSeCd === next[index]?.acntgSeCd) + ); +} + function BusinessCostFormSection() { const { control, setValue, getValues } = useFormContext(); const bizCstList = useWatch({ control, name: "bizCstList" }) ?? []; @@ -42,22 +52,22 @@ function BusinessCostFormSection() { useEffect(() => { if (costItems.length === 0) return; - const existingMap = new Map(getValues("bizCstList").map((item) => [item.acntgSeCd, item])); + const current = getValues("bizCstList") ?? []; + const existingMap = new Map(current.map((item) => [item.acntgSeCd, item])); + const next = costItems.map((item) => { + const existing = existingMap.get(item.code); + if (existing) return existing; - setValue( - "bizCstList", - costItems.map((item) => { - const existing = existingMap.get(item.code); - if (existing) return existing; + return { + acntgSeCd: item.code, + bscPlanAmt: 0, + bgtRfltAmt: 0, + tstmDesignAmt: 0, + }; + }); - return { - acntgSeCd: item.code, - bscPlanAmt: 0, - bgtRfltAmt: 0, - tstmDesignAmt: 0, - }; - }), - ); + if (isSameBizCstCodes(current, next)) return; + setValue("bizCstList", next); }, [costItems, getValues, setValue]); function handleCostChange(index: number, fieldKey: CostFieldKey, value: string) { @@ -119,58 +129,37 @@ function BusinessCostFormSection() {
- ( - - handleCostChange(idx, "bgtRfltAmt", e.target.value)} - readOnly={item.readonly} - aria-invalid={fieldState.invalid} - className={`text-right ${item.readonly ? "bg-muted" : ""}`} - /> - - )} - /> + + handleCostChange(idx, "bgtRfltAmt", e.target.value)} + readOnly={item.readonly} + className={`text-right ${item.readonly ? "bg-muted" : ""}`} + /> + - ( - - handleCostChange(idx, "bscPlanAmt", e.target.value)} - readOnly={item.readonly} - aria-invalid={fieldState.invalid} - className={`text-right ${item.readonly ? "bg-muted" : ""}`} - /> - - )} - /> + + handleCostChange(idx, "bscPlanAmt", e.target.value)} + readOnly={item.readonly} + className={`text-right ${item.readonly ? "bg-muted" : ""}`} + /> + - ( - - handleCostChange(idx, "tstmDesignAmt", e.target.value)} - readOnly={item.readonly} - aria-invalid={fieldState.invalid} - className={`text-right ${item.readonly ? "bg-muted" : ""}`} - /> - - )} - /> + + handleCostChange(idx, "tstmDesignAmt", e.target.value)} + readOnly={item.readonly} + className={`text-right ${item.readonly ? "bg-muted" : ""}`} + /> +
diff --git a/apps/as/src/widgets/pbadms-sprt/ui/business-scale-form-section.tsx b/apps/as/src/widgets/pbadms-sprt/ui/business-scale-form-section.tsx index 2bee5e104..de479a0cf 100644 --- a/apps/as/src/widgets/pbadms-sprt/ui/business-scale-form-section.tsx +++ b/apps/as/src/widgets/pbadms-sprt/ui/business-scale-form-section.tsx @@ -1,7 +1,7 @@ "use client"; import { useEffect, useMemo } from "react"; -import { Controller, useFormContext, useWatch } from "react-hook-form"; +import { useFormContext, useWatch } from "react-hook-form"; import { SectionCollapsible } from "@/shared/ui"; import { Field } from "@/shared/ui/field"; @@ -17,7 +17,7 @@ import { TableRow, } from "@common/components"; -import { buildCnstpScaleItems, recalculateBzScaleTotals } from "../lib/business-scale-form-utils"; +import { buildCnstpScaleItems, recalculateBzScaleTotals } from "../lib/business-scale-form-utils"; interface BusinessScaleFormData { bzScaleList: Array<{ @@ -31,6 +31,16 @@ interface BusinessScaleFormData { type ScaleFieldKey = "bscPl" | "budgetReflct" | "thtiDsgn"; +function isSameBzScaleCodes( + current: BusinessScaleFormData["bzScaleList"], + next: BusinessScaleFormData["bzScaleList"], +) { + return ( + current.length === next.length && + current.every((item, index) => item.cnstpSeCd === next[index]?.cnstpSeCd) + ); +} + function BusinessScaleFormSection() { const { control, setValue, getValues } = useFormContext(); const bzScaleList = useWatch({ control, name: "bzScaleList" }) ?? []; @@ -42,7 +52,8 @@ function BusinessScaleFormSection() { useEffect(() => { if (scaleItems.length === 0) return; - const existingMap = new Map(getValues("bzScaleList").map((item) => [item.cnstpSeCd, item])); + const current = getValues("bzScaleList") ?? []; + const existingMap = new Map(current.map((item) => [item.cnstpSeCd, item])); const next = scaleItems.map((item) => { const existing = existingMap.get(item.code); @@ -57,6 +68,7 @@ function BusinessScaleFormSection() { }; }); + if (isSameBzScaleCodes(current, next)) return; setValue("bzScaleList", recalculateBzScaleTotals(next, scaleItems)); }, [scaleItems, getValues, setValue]); @@ -112,58 +124,37 @@ function BusinessScaleFormSection() {
- ( - - handleScaleChange(idx, "bscPl", e.target.value)} - readOnly={item.readonly} - aria-invalid={fieldState.invalid} - className={`text-right ${item.readonly ? "bg-muted" : ""}`} - /> - - )} - /> + + handleScaleChange(idx, "bscPl", e.target.value)} + readOnly={item.readonly} + className={`text-right ${item.readonly ? "bg-muted" : ""}`} + /> + - ( - - handleScaleChange(idx, "budgetReflct", e.target.value)} - readOnly={item.readonly} - aria-invalid={fieldState.invalid} - className={`text-right ${item.readonly ? "bg-muted" : ""}`} - /> - - )} - /> + + handleScaleChange(idx, "budgetReflct", e.target.value)} + readOnly={item.readonly} + className={`text-right ${item.readonly ? "bg-muted" : ""}`} + /> + - ( - - handleScaleChange(idx, "thtiDsgn", e.target.value)} - readOnly={item.readonly} - aria-invalid={fieldState.invalid} - className={`text-right ${item.readonly ? "bg-muted" : ""}`} - /> - - )} - /> + + handleScaleChange(idx, "thtiDsgn", e.target.value)} + readOnly={item.readonly} + className={`text-right ${item.readonly ? "bg-muted" : ""}`} + /> +
diff --git a/apps/as/src/widgets/pbadms-sprt/ui/checklist-form-section.tsx b/apps/as/src/widgets/pbadms-sprt/ui/checklist-form-section.tsx index 1e4c324bb..0fb9e136a 100644 --- a/apps/as/src/widgets/pbadms-sprt/ui/checklist-form-section.tsx +++ b/apps/as/src/widgets/pbadms-sprt/ui/checklist-form-section.tsx @@ -1,18 +1,15 @@ "use client"; import { useEffect, useMemo, useState } from "react"; -import { Controller, useFormContext, useWatch } from "react-hook-form"; +import { useFormContext, useWatch } from "react-hook-form"; import { useQuery } from "@tanstack/react-query"; import { SectionCollapsible } from "@/shared/ui"; -import { Field, FieldLabel } from "@/shared/ui/field"; import { cklstMngService, type CklstTreeNode } from "@/entities/cklst"; import { useCodeList } from "@/entities/code"; import { Button, - RadioGroup, - RadioGroupItem, Skeleton, Table, TableBody, @@ -125,11 +122,21 @@ function ChecklistFormSection({ docSeComCd, fieldPrefix }: ChecklistFormSectionP } }, [checklistTabs, activeTab]); + function handleSelectChange(resultIdx: number, value: "Y" | "N") { + const currentList = getValues("cklstResultList") as CklstResultListItem[]; + const row = currentList[resultIdx]; + if (!row) return; + + const newList = [...currentList]; + newList[resultIdx] = { ...row, selected: value }; + setValue("cklstResultList", newList, { shouldDirty: true }); + } + function handleSelectAll(tabCode: string, value: "Y" | "N") { const tab = checklistTabs.find((t) => t.code === tabCode); if (!tab) return; - const currentList = getValues("cklstResultList"); + const currentList = getValues("cklstResultList") as CklstResultListItem[]; const newList = [...currentList]; tab.items @@ -141,7 +148,7 @@ function ChecklistFormSection({ docSeComCd, fieldPrefix }: ChecklistFormSectionP } }); - setValue("cklstResultList", newList); + setValue("cklstResultList", newList, { shouldDirty: true }); } const hasSelectedTaskCodes = Boolean(taskSeComCdArray?.length); @@ -234,6 +241,8 @@ function ChecklistFormSection({ docSeComCd, fieldPrefix }: ChecklistFormSectionP {tab.items.map((item) => { const resultIdx = cklstRvwSnIndexMap.get(item.cklstRvwSn) ?? -1; const showRadios = item.bchckUseYn === "Y"; + const selected = cklstResultList[resultIdx]?.selected; + const radioName = `check-${item.cklstRvwSn}`; return ( @@ -248,66 +257,30 @@ function ChecklistFormSection({ docSeComCd, fieldPrefix }: ChecklistFormSectionP {item.content} {showRadios && resultIdx >= 0 ? ( - ( - <> - - - - - - - - - - - - - - - - - - - - )} - /> + <> + + handleSelectChange(resultIdx, "Y")} + aria-label="예" + className="cursor-pointer" + /> + + + handleSelectChange(resultIdx, "N")} + aria-label="아니오" + className="cursor-pointer" + /> + + ) : null} ); diff --git a/apps/as/src/widgets/pbadms-sprt/ui/fnrs-shar-form-section.tsx b/apps/as/src/widgets/pbadms-sprt/ui/fnrs-shar-form-section.tsx index 5109dbfe7..baff36f8b 100644 --- a/apps/as/src/widgets/pbadms-sprt/ui/fnrs-shar-form-section.tsx +++ b/apps/as/src/widgets/pbadms-sprt/ui/fnrs-shar-form-section.tsx @@ -1,7 +1,7 @@ "use client"; import { useEffect, useMemo, useState } from "react"; -import { Controller, useFormContext, useWatch } from "react-hook-form"; +import { useFormContext, useWatch } from "react-hook-form"; import { getDefaultFileKey, useTus } from "@/shared/lib"; import { AttachmentFileField, SectionCollapsible } from "@/shared/ui"; @@ -78,6 +78,13 @@ function toInitialSharFile(file?: AtflMngClientResponseDto): SharUploadedFile | }; } +function isSameFnrsSharCodes(current: FnrsSharListItem[], next: FnrsSharListItem[]) { + return ( + current.length === next.length && + current.every((item, index) => item.fnrsSharSeCd === next[index]?.fnrsSharSeCd) + ); +} + function FnrsSharFormSection({ initialSharTree, initialSharAtchFiles }: FnrsSharFormSectionProps) { const { control, setValue, getValues } = useFormContext(); const fnrsSharList = useWatch({ control, name: "fnrsSharList" }) ?? []; @@ -117,6 +124,7 @@ function FnrsSharFormSection({ initialSharTree, initialSharAtchFiles }: FnrsShar : mergeFnrsSharTreeWithForm(sharTree, []); const withTotals = recalculateFnrsSharTotals(merged, sharItems); + if (isSameFnrsSharCodes(existing, withTotals)) return; setValue("fnrsSharList", withTotals); }, [sharItems, sharTree, initialSharTree, getValues, setValue]); @@ -226,40 +234,26 @@ function FnrsSharFormSection({ initialSharTree, initialSharAtchFiles }: FnrsShar {item.label} - ( - - handleSharChange(idx, "exstnAmt", e.target.value)} - readOnly={item.readonly} - aria-invalid={fieldState.invalid} - className={cn("text-right", item.readonly && "bg-muted")} - /> - - )} - /> + + handleSharChange(idx, "exstnAmt", e.target.value)} + readOnly={item.readonly} + className={cn("text-right", item.readonly && "bg-muted")} + /> + - ( - - handleSharChange(idx, "chgRqtAmt", e.target.value)} - readOnly={item.readonly} - aria-invalid={fieldState.invalid} - className={cn("text-right", item.readonly && "bg-muted")} - /> - - )} - /> + + handleSharChange(idx, "chgRqtAmt", e.target.value)} + readOnly={item.readonly} + className={cn("text-right", item.readonly && "bg-muted")} + /> +
@@ -270,19 +264,12 @@ function FnrsSharFormSection({ initialSharTree, initialSharAtchFiles }: FnrsShar
{calcDiffPercent(diff, data.exstnAmt || 0)}
- ( - - handleSharChange(idx, "rmrkCn", e.target.value)} - aria-invalid={fieldState.invalid} - /> - - )} - /> + + handleSharChange(idx, "rmrkCn", e.target.value)} + /> + );