[as] refactor: 재원협의 폼 useWatch + Controller 방식 제거

This commit is contained in:
2026-09-22 10:57:41 +09:00
parent 87fbf15e2b
commit 0dd2111cb9
5 changed files with 243 additions and 309 deletions
@@ -1,7 +1,7 @@
"use client"; "use client";
import { useEffect, useRef, useState } from "react"; 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 { useQueryClient } from "@tanstack/react-query";
import { Download } from "lucide-react"; import { Download } from "lucide-react";
@@ -78,7 +78,7 @@ function AttachmentsFormSection({
initialEsntlFiles, initialEsntlFiles,
initialEtcFiles, initialEtcFiles,
}: AttachmentsFormSectionProps) { }: AttachmentsFormSectionProps) {
const { control, getValues, setValue } = useFormContext<AttachmentsFormData>(); const { getValues, setValue } = useFormContext<AttachmentsFormData>();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const [uploadedFiles, setUploadedFiles] = useState<Record<string, UploadedFileItem[]>>({}); const [uploadedFiles, setUploadedFiles] = useState<Record<string, UploadedFileItem[]>>({});
const [etcFiles, setEtcFiles] = useState<EtcFileItem[]>([]); const [etcFiles, setEtcFiles] = useState<EtcFileItem[]>([]);
@@ -373,89 +373,83 @@ function AttachmentsFormSection({
return ( return (
<SectionCollapsible title="첨부파일"> <SectionCollapsible title="첨부파일">
<div className="space-y-6"> <div className="space-y-6">
<Controller <div className="px-2">
name="esntlAtchFileList" <h3 className="mb-4 text-xl font-semibold">필수 첨부파일</h3>
control={control} <Table variant="secondary" className="table-fixed">
render={() => ( <colgroup>
<div className="px-2"> <col width="44%" />
<h3 className="mb-4 text-xl font-semibold">필수 첨부파일</h3> <col width="12%" />
<Table variant="secondary" className="table-fixed"> <col width="44%" />
<colgroup> </colgroup>
<col width="44%" /> <TableHeader>
<col width="12%" /> <TableRow>
<col width="44%" /> <TableHead className="w-[30rem]" align="left">
</colgroup> 파일명
<TableHeader> </TableHead>
<TableRow> <TableHead>파일 확장자</TableHead>
<TableHead className="w-[30rem]" align="left"> <TableHead>파일첨부</TableHead>
파일명 </TableRow>
</TableHead> </TableHeader>
<TableHead>파일 확장자</TableHead> <TableBody>
<TableHead>파일첨부</TableHead> {essentialFiles.length === 0 ? (
</TableRow> <TableRow>
</TableHeader> <TableCell colSpan={4} className="py-12 text-center text-muted-foreground">
<TableBody> 조회된 필수 첨부파일 항목이 없습니다.
{essentialFiles.length === 0 ? ( </TableCell>
<TableRow> </TableRow>
<TableCell colSpan={4} className="py-12 text-center text-muted-foreground"> ) : (
조회된 필수 첨부파일 항목이 없습니다. essentialFiles.map((file) => {
const atflCd = file.atchFileGroupId ?? "";
return (
<TableRow key={file.esntlAtchFileId}>
<TableHead
className="group break-keep font-medium"
data-required={file.esntlAtchYn === "Y"}
align="left"
>
<div className="inline-flex w-[calc(100%-1rem)] items-center justify-between gap-10">
{file.esntlAtchFileNm}
{atflCd ? (
<Button
size="sm"
color="white"
onClick={() => downloadTemplate(atflCd)}
>
<Download className="!size-5 shrink-0" />
양식내려받기
</Button>
) : null}
</div>
</TableHead>
<TableCell align="center">{file.esntlAtchFileRmrk}</TableCell>
<TableCell className="space-y-2">
<AttachmentFileField
files={(uploadedFiles[file.esntlAtchFileId] ?? []).map((item) => ({
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)
}
/>
</TableCell> </TableCell>
</TableRow> </TableRow>
) : ( );
essentialFiles.map((file) => { })
const atflCd = file.atchFileGroupId ?? ""; )}
</TableBody>
return ( </Table>
<TableRow key={file.esntlAtchFileId}> </div>
<TableHead
className="group break-keep font-medium"
data-required={file.esntlAtchYn === "Y"}
align="left"
>
<div className="inline-flex w-[calc(100%-1rem)] items-center justify-between gap-10">
{file.esntlAtchFileNm}
{atflCd ? (
<Button
size="sm"
color="white"
onClick={() => downloadTemplate(atflCd)}
>
<Download className="!size-5 shrink-0" />
양식내려받기
</Button>
) : null}
</div>
</TableHead>
<TableCell align="center">{file.esntlAtchFileRmrk}</TableCell>
<TableCell className="space-y-2">
<AttachmentFileField
files={(uploadedFiles[file.esntlAtchFileId] ?? []).map((item) => ({
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)
}
/>
</TableCell>
</TableRow>
);
})
)}
</TableBody>
</Table>
</div>
)}
/>
<div className="px-2"> <div className="px-2">
<div className="mb-4 flex items-center justify-between"> <div className="mb-4 flex items-center justify-between">
@@ -1,7 +1,7 @@
"use client"; "use client";
import { useEffect, useMemo } from "react"; 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 { SectionCollapsible } from "@/shared/ui";
import { Field } from "@/shared/ui/field"; import { Field } from "@/shared/ui/field";
@@ -31,6 +31,16 @@ interface BusinessCostFormData {
type CostFieldKey = "bscPlanAmt" | "bgtRfltAmt" | "tstmDesignAmt"; 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() { function BusinessCostFormSection() {
const { control, setValue, getValues } = useFormContext<BusinessCostFormData>(); const { control, setValue, getValues } = useFormContext<BusinessCostFormData>();
const bizCstList = useWatch({ control, name: "bizCstList" }) ?? []; const bizCstList = useWatch({ control, name: "bizCstList" }) ?? [];
@@ -42,22 +52,22 @@ function BusinessCostFormSection() {
useEffect(() => { useEffect(() => {
if (costItems.length === 0) return; 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( return {
"bizCstList", acntgSeCd: item.code,
costItems.map((item) => { bscPlanAmt: 0,
const existing = existingMap.get(item.code); bgtRfltAmt: 0,
if (existing) return existing; tstmDesignAmt: 0,
};
});
return { if (isSameBizCstCodes(current, next)) return;
acntgSeCd: item.code, setValue("bizCstList", next);
bscPlanAmt: 0,
bgtRfltAmt: 0,
tstmDesignAmt: 0,
};
}),
);
}, [costItems, getValues, setValue]); }, [costItems, getValues, setValue]);
function handleCostChange(index: number, fieldKey: CostFieldKey, value: string) { function handleCostChange(index: number, fieldKey: CostFieldKey, value: string) {
@@ -119,58 +129,37 @@ function BusinessCostFormSection() {
</div> </div>
</TableHead> </TableHead>
<TableCell> <TableCell>
<Controller <Field>
name={`bizCstList.${idx}.bgtRfltAmt`} <Input
control={control} type="number"
render={({ field, fieldState }) => ( value={data.bgtRfltAmt ?? ""}
<Field data-invalid={fieldState.invalid}> onChange={(e) => handleCostChange(idx, "bgtRfltAmt", e.target.value)}
<Input readOnly={item.readonly}
type="number" className={`text-right ${item.readonly ? "bg-muted" : ""}`}
value={field.value || ""} />
onChange={(e) => handleCostChange(idx, "bgtRfltAmt", e.target.value)} </Field>
readOnly={item.readonly}
aria-invalid={fieldState.invalid}
className={`text-right ${item.readonly ? "bg-muted" : ""}`}
/>
</Field>
)}
/>
</TableCell> </TableCell>
<TableCell> <TableCell>
<Controller <Field>
name={`bizCstList.${idx}.bscPlanAmt`} <Input
control={control} type="number"
render={({ field, fieldState }) => ( value={data.bscPlanAmt ?? ""}
<Field data-invalid={fieldState.invalid}> onChange={(e) => handleCostChange(idx, "bscPlanAmt", e.target.value)}
<Input readOnly={item.readonly}
type="number" className={`text-right ${item.readonly ? "bg-muted" : ""}`}
value={field.value || ""} />
onChange={(e) => handleCostChange(idx, "bscPlanAmt", e.target.value)} </Field>
readOnly={item.readonly}
aria-invalid={fieldState.invalid}
className={`text-right ${item.readonly ? "bg-muted" : ""}`}
/>
</Field>
)}
/>
</TableCell> </TableCell>
<TableCell> <TableCell>
<Controller <Field>
name={`bizCstList.${idx}.tstmDesignAmt`} <Input
control={control} type="number"
render={({ field, fieldState }) => ( value={data.tstmDesignAmt ?? ""}
<Field data-invalid={fieldState.invalid}> onChange={(e) => handleCostChange(idx, "tstmDesignAmt", e.target.value)}
<Input readOnly={item.readonly}
type="number" className={`text-right ${item.readonly ? "bg-muted" : ""}`}
value={field.value || ""} />
onChange={(e) => handleCostChange(idx, "tstmDesignAmt", e.target.value)} </Field>
readOnly={item.readonly}
aria-invalid={fieldState.invalid}
className={`text-right ${item.readonly ? "bg-muted" : ""}`}
/>
</Field>
)}
/>
</TableCell> </TableCell>
<TableCell> <TableCell>
<div className="text-right"> <div className="text-right">
@@ -1,7 +1,7 @@
"use client"; "use client";
import { useEffect, useMemo } from "react"; 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 { SectionCollapsible } from "@/shared/ui";
import { Field } from "@/shared/ui/field"; import { Field } from "@/shared/ui/field";
@@ -17,7 +17,7 @@ import {
TableRow, TableRow,
} from "@common/components"; } from "@common/components";
import { buildCnstpScaleItems, recalculateBzScaleTotals } from "../lib/business-scale-form-utils"; import { buildCnstpScaleItems, recalculateBzScaleTotals } from "../lib/business-scale-form-utils";
interface BusinessScaleFormData { interface BusinessScaleFormData {
bzScaleList: Array<{ bzScaleList: Array<{
@@ -31,6 +31,16 @@ interface BusinessScaleFormData {
type ScaleFieldKey = "bscPl" | "budgetReflct" | "thtiDsgn"; 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() { function BusinessScaleFormSection() {
const { control, setValue, getValues } = useFormContext<BusinessScaleFormData>(); const { control, setValue, getValues } = useFormContext<BusinessScaleFormData>();
const bzScaleList = useWatch({ control, name: "bzScaleList" }) ?? []; const bzScaleList = useWatch({ control, name: "bzScaleList" }) ?? [];
@@ -42,7 +52,8 @@ function BusinessScaleFormSection() {
useEffect(() => { useEffect(() => {
if (scaleItems.length === 0) return; 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 next = scaleItems.map((item) => {
const existing = existingMap.get(item.code); const existing = existingMap.get(item.code);
@@ -57,6 +68,7 @@ function BusinessScaleFormSection() {
}; };
}); });
if (isSameBzScaleCodes(current, next)) return;
setValue("bzScaleList", recalculateBzScaleTotals(next, scaleItems)); setValue("bzScaleList", recalculateBzScaleTotals(next, scaleItems));
}, [scaleItems, getValues, setValue]); }, [scaleItems, getValues, setValue]);
@@ -112,58 +124,37 @@ function BusinessScaleFormSection() {
</div> </div>
</TableHead> </TableHead>
<TableCell> <TableCell>
<Controller <Field>
name={`bzScaleList.${idx}.bscPl`} <Input
control={control} type="number"
render={({ field, fieldState }) => ( value={data.bscPl ?? ""}
<Field data-invalid={fieldState.invalid}> onChange={(e) => handleScaleChange(idx, "bscPl", e.target.value)}
<Input readOnly={item.readonly}
type="number" className={`text-right ${item.readonly ? "bg-muted" : ""}`}
value={field.value || ""} />
onChange={(e) => handleScaleChange(idx, "bscPl", e.target.value)} </Field>
readOnly={item.readonly}
aria-invalid={fieldState.invalid}
className={`text-right ${item.readonly ? "bg-muted" : ""}`}
/>
</Field>
)}
/>
</TableCell> </TableCell>
<TableCell> <TableCell>
<Controller <Field>
name={`bzScaleList.${idx}.budgetReflct`} <Input
control={control} type="number"
render={({ field, fieldState }) => ( value={data.budgetReflct ?? ""}
<Field data-invalid={fieldState.invalid}> onChange={(e) => handleScaleChange(idx, "budgetReflct", e.target.value)}
<Input readOnly={item.readonly}
type="number" className={`text-right ${item.readonly ? "bg-muted" : ""}`}
value={field.value || ""} />
onChange={(e) => handleScaleChange(idx, "budgetReflct", e.target.value)} </Field>
readOnly={item.readonly}
aria-invalid={fieldState.invalid}
className={`text-right ${item.readonly ? "bg-muted" : ""}`}
/>
</Field>
)}
/>
</TableCell> </TableCell>
<TableCell> <TableCell>
<Controller <Field>
name={`bzScaleList.${idx}.thtiDsgn`} <Input
control={control} type="number"
render={({ field, fieldState }) => ( value={data.thtiDsgn ?? ""}
<Field data-invalid={fieldState.invalid}> onChange={(e) => handleScaleChange(idx, "thtiDsgn", e.target.value)}
<Input readOnly={item.readonly}
type="number" className={`text-right ${item.readonly ? "bg-muted" : ""}`}
value={field.value || ""} />
onChange={(e) => handleScaleChange(idx, "thtiDsgn", e.target.value)} </Field>
readOnly={item.readonly}
aria-invalid={fieldState.invalid}
className={`text-right ${item.readonly ? "bg-muted" : ""}`}
/>
</Field>
)}
/>
</TableCell> </TableCell>
<TableCell> <TableCell>
<div className="text-right"> <div className="text-right">
@@ -1,18 +1,15 @@
"use client"; "use client";
import { useEffect, useMemo, useState } from "react"; 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 { useQuery } from "@tanstack/react-query";
import { SectionCollapsible } from "@/shared/ui"; import { SectionCollapsible } from "@/shared/ui";
import { Field, FieldLabel } from "@/shared/ui/field";
import { cklstMngService, type CklstTreeNode } from "@/entities/cklst"; import { cklstMngService, type CklstTreeNode } from "@/entities/cklst";
import { useCodeList } from "@/entities/code"; import { useCodeList } from "@/entities/code";
import { import {
Button, Button,
RadioGroup,
RadioGroupItem,
Skeleton, Skeleton,
Table, Table,
TableBody, TableBody,
@@ -125,11 +122,21 @@ function ChecklistFormSection({ docSeComCd, fieldPrefix }: ChecklistFormSectionP
} }
}, [checklistTabs, activeTab]); }, [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") { function handleSelectAll(tabCode: string, value: "Y" | "N") {
const tab = checklistTabs.find((t) => t.code === tabCode); const tab = checklistTabs.find((t) => t.code === tabCode);
if (!tab) return; if (!tab) return;
const currentList = getValues("cklstResultList"); const currentList = getValues("cklstResultList") as CklstResultListItem[];
const newList = [...currentList]; const newList = [...currentList];
tab.items tab.items
@@ -141,7 +148,7 @@ function ChecklistFormSection({ docSeComCd, fieldPrefix }: ChecklistFormSectionP
} }
}); });
setValue("cklstResultList", newList); setValue("cklstResultList", newList, { shouldDirty: true });
} }
const hasSelectedTaskCodes = Boolean(taskSeComCdArray?.length); const hasSelectedTaskCodes = Boolean(taskSeComCdArray?.length);
@@ -234,6 +241,8 @@ function ChecklistFormSection({ docSeComCd, fieldPrefix }: ChecklistFormSectionP
{tab.items.map((item) => { {tab.items.map((item) => {
const resultIdx = cklstRvwSnIndexMap.get(item.cklstRvwSn) ?? -1; const resultIdx = cklstRvwSnIndexMap.get(item.cklstRvwSn) ?? -1;
const showRadios = item.bchckUseYn === "Y"; const showRadios = item.bchckUseYn === "Y";
const selected = cklstResultList[resultIdx]?.selected;
const radioName = `check-${item.cklstRvwSn}`;
return ( return (
<TableRow key={item.cklstRvwSn}> <TableRow key={item.cklstRvwSn}>
@@ -248,66 +257,30 @@ function ChecklistFormSection({ docSeComCd, fieldPrefix }: ChecklistFormSectionP
{item.content} {item.content}
</TableCell> </TableCell>
{showRadios && resultIdx >= 0 ? ( {showRadios && resultIdx >= 0 ? (
<Controller <>
name={`cklstResultList.${resultIdx}.selected`} <TableCell className="text-center">
control={control} <input
render={({ field, fieldState }) => ( type="radio"
<> name={radioName}
<TableCell className="text-center"> value="Y"
<RadioGroup checked={selected === "Y"}
name={field.name} onChange={() => handleSelectChange(resultIdx, "Y")}
value={field.value === "Y" ? "Y" : undefined} aria-label="예"
onValueChange={field.onChange} className="cursor-pointer"
className="flex justify-center" />
> </TableCell>
<FieldLabel <TableCell className="text-center">
htmlFor={`check-${item.cklstRvwSn}-Y`} <input
className="!w-auto justify-center border-none" type="radio"
> name={radioName}
<Field value="N"
orientation="horizontal" checked={selected === "N"}
data-invalid={fieldState.invalid} onChange={() => handleSelectChange(resultIdx, "N")}
className="w-auto" aria-label="아니오"
> className="cursor-pointer"
<RadioGroupItem />
value="Y" </TableCell>
id={`check-${item.cklstRvwSn}-Y`} </>
aria-invalid={fieldState.invalid}
aria-label="예"
/>
</Field>
</FieldLabel>
</RadioGroup>
</TableCell>
<TableCell className="text-center">
<RadioGroup
name={field.name}
value={field.value === "N" ? "N" : undefined}
onValueChange={field.onChange}
className="flex justify-center"
>
<FieldLabel
htmlFor={`check-${item.cklstRvwSn}-N`}
className="!w-auto justify-center border-none"
>
<Field
orientation="horizontal"
data-invalid={fieldState.invalid}
className="w-auto"
>
<RadioGroupItem
value="N"
id={`check-${item.cklstRvwSn}-N`}
aria-invalid={fieldState.invalid}
aria-label="아니오"
/>
</Field>
</FieldLabel>
</RadioGroup>
</TableCell>
</>
)}
/>
) : null} ) : null}
</TableRow> </TableRow>
); );
@@ -1,7 +1,7 @@
"use client"; "use client";
import { useEffect, useMemo, useState } from "react"; 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 { getDefaultFileKey, useTus } from "@/shared/lib";
import { AttachmentFileField, SectionCollapsible } from "@/shared/ui"; 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) { function FnrsSharFormSection({ initialSharTree, initialSharAtchFiles }: FnrsSharFormSectionProps) {
const { control, setValue, getValues } = useFormContext<FnrsSharFormData>(); const { control, setValue, getValues } = useFormContext<FnrsSharFormData>();
const fnrsSharList = useWatch({ control, name: "fnrsSharList" }) ?? []; const fnrsSharList = useWatch({ control, name: "fnrsSharList" }) ?? [];
@@ -117,6 +124,7 @@ function FnrsSharFormSection({ initialSharTree, initialSharAtchFiles }: FnrsShar
: mergeFnrsSharTreeWithForm(sharTree, []); : mergeFnrsSharTreeWithForm(sharTree, []);
const withTotals = recalculateFnrsSharTotals(merged, sharItems); const withTotals = recalculateFnrsSharTotals(merged, sharItems);
if (isSameFnrsSharCodes(existing, withTotals)) return;
setValue("fnrsSharList", withTotals); setValue("fnrsSharList", withTotals);
}, [sharItems, sharTree, initialSharTree, getValues, setValue]); }, [sharItems, sharTree, initialSharTree, getValues, setValue]);
@@ -226,40 +234,26 @@ function FnrsSharFormSection({ initialSharTree, initialSharAtchFiles }: FnrsShar
{item.label} {item.label}
</TableHead> </TableHead>
<TableCell> <TableCell>
<Controller <Field>
name={`fnrsSharList.${idx}.exstnAmt`} <Input
control={control} type="number"
render={({ field, fieldState }) => ( value={data.exstnAmt ?? ""}
<Field data-invalid={fieldState.invalid}> onChange={(e) => handleSharChange(idx, "exstnAmt", e.target.value)}
<Input readOnly={item.readonly}
type="number" className={cn("text-right", item.readonly && "bg-muted")}
value={field.value ?? ""} />
onChange={(e) => handleSharChange(idx, "exstnAmt", e.target.value)} </Field>
readOnly={item.readonly}
aria-invalid={fieldState.invalid}
className={cn("text-right", item.readonly && "bg-muted")}
/>
</Field>
)}
/>
</TableCell> </TableCell>
<TableCell> <TableCell>
<Controller <Field>
name={`fnrsSharList.${idx}.chgRqtAmt`} <Input
control={control} type="number"
render={({ field, fieldState }) => ( value={data.chgRqtAmt ?? ""}
<Field data-invalid={fieldState.invalid}> onChange={(e) => handleSharChange(idx, "chgRqtAmt", e.target.value)}
<Input readOnly={item.readonly}
type="number" className={cn("text-right", item.readonly && "bg-muted")}
value={field.value ?? ""} />
onChange={(e) => handleSharChange(idx, "chgRqtAmt", e.target.value)} </Field>
readOnly={item.readonly}
aria-invalid={fieldState.invalid}
className={cn("text-right", item.readonly && "bg-muted")}
/>
</Field>
)}
/>
</TableCell> </TableCell>
<TableCell> <TableCell>
<div className="text-right"> <div className="text-right">
@@ -270,19 +264,12 @@ function FnrsSharFormSection({ initialSharTree, initialSharAtchFiles }: FnrsShar
<div className="text-right">{calcDiffPercent(diff, data.exstnAmt || 0)}</div> <div className="text-right">{calcDiffPercent(diff, data.exstnAmt || 0)}</div>
</TableCell> </TableCell>
<TableCell> <TableCell>
<Controller <Field>
name={`fnrsSharList.${idx}.rmrkCn`} <Input
control={control} value={data.rmrkCn ?? ""}
render={({ field, fieldState }) => ( onChange={(e) => handleSharChange(idx, "rmrkCn", e.target.value)}
<Field data-invalid={fieldState.invalid}> />
<Input </Field>
value={field.value ?? ""}
onChange={(e) => handleSharChange(idx, "rmrkCn", e.target.value)}
aria-invalid={fieldState.invalid}
/>
</Field>
)}
/>
</TableCell> </TableCell>
</TableRow> </TableRow>
); );