diff --git a/apps/as/src/pages/sewer-bsc-plan/ui/sections/biz-cst-matrix-form-section.tsx b/apps/as/src/pages/sewer-bsc-plan/ui/sections/biz-cst-matrix-form-section.tsx index 710a81fcf..a5ebdae97 100644 --- a/apps/as/src/pages/sewer-bsc-plan/ui/sections/biz-cst-matrix-form-section.tsx +++ b/apps/as/src/pages/sewer-bsc-plan/ui/sections/biz-cst-matrix-form-section.tsx @@ -3,7 +3,7 @@ import { useEffect, useMemo } from "react"; import { useFormContext, useWatch } from "react-hook-form"; -import { NON_NEGATIVE_INPUT_PROPS, stripMinusSign } from "@/shared/lib"; +import { NON_NEGATIVE_INPUT_PROPS, sanitizeNumberInput } from "@/shared/lib"; import { SectionCollapsible } from "@/shared/ui"; import { Field } from "@/shared/ui/field"; import { useAcntgSeMngTree } from "@/entities/acntg"; @@ -51,7 +51,7 @@ function BizCstMatrixFormSection() { ]; function handleChange(acntgSeCd: string, planStpNo: number, value: string) { - const normalized = truncateDecimals(stripMinusSign(value), 3); + const normalized = truncateDecimals(sanitizeNumberInput(value), 3); const numValue = Number(normalized.replace(/,/g, "")) || 0; const next = recalculateBizCstMatrix( setAmt(getValues("bizCstList"), acntgSeCd, planStpNo, numValue), diff --git a/apps/as/src/pages/sewer-bsc-plan/ui/sections/fnrs-cst-form-section.tsx b/apps/as/src/pages/sewer-bsc-plan/ui/sections/fnrs-cst-form-section.tsx index 7ef5947f5..f4be7a396 100644 --- a/apps/as/src/pages/sewer-bsc-plan/ui/sections/fnrs-cst-form-section.tsx +++ b/apps/as/src/pages/sewer-bsc-plan/ui/sections/fnrs-cst-form-section.tsx @@ -3,7 +3,7 @@ import { useMemo } from "react"; import { useFormContext, useWatch } from "react-hook-form"; -import { NON_NEGATIVE_INPUT_PROPS, stripMinusSign } from "@/shared/lib"; +import { NON_NEGATIVE_INPUT_PROPS, sanitizeNumberInput } from "@/shared/lib"; import { SectionCollapsible } from "@/shared/ui"; import { Field } from "@/shared/ui/field"; import { useAcntgSeMngTree } from "@/entities/acntg"; @@ -59,7 +59,7 @@ function FnrsCstFormSection() { ); function handleChange(planStpNo: number, fieldKey: EditableFnrsKey, value: string) { - const numValue = Number(stripMinusSign(value).replace(/,/g, "")) || 0; + const numValue = Number(sanitizeNumberInput(value).replace(/,/g, "")) || 0; const draft = [...getValues("fnrsCstList")]; const index = draft.findIndex((item) => item.planStpNo === planStpNo); if (index < 0) return; diff --git a/apps/as/src/pages/sewer-bsc-plan/ui/sections/outline-form-section.tsx b/apps/as/src/pages/sewer-bsc-plan/ui/sections/outline-form-section.tsx index 6375139a0..50bb4d629 100644 --- a/apps/as/src/pages/sewer-bsc-plan/ui/sections/outline-form-section.tsx +++ b/apps/as/src/pages/sewer-bsc-plan/ui/sections/outline-form-section.tsx @@ -4,7 +4,7 @@ import { useState } from "react"; import { useFormContext, useWatch } from "react-hook-form"; import dynamic from "next/dynamic"; -import { NON_NEGATIVE_INPUT_PROPS, stripMinusSign } from "@/shared/lib"; +import { NON_NEGATIVE_INPUT_PROPS, sanitizeNumberInput } from "@/shared/lib"; import { SectionCollapsible } from "@/shared/ui"; import { Field } from "@/shared/ui/field"; import { truncateDecimals } from "@/widgets/pbadms-sprt"; @@ -62,8 +62,8 @@ function OutlineFormSection() { value: string, decimals?: number, ) { - const unsigned = stripMinusSign(value); - const normalized = decimals === undefined ? unsigned : truncateDecimals(unsigned, decimals); + const sanitized = sanitizeNumberInput(value); + const normalized = decimals === undefined ? sanitized : truncateDecimals(sanitized, decimals); const numValue = Number(normalized.replace(/,/g, "")) || 0; const draft = [...getValues("sewerBscPlanOtlnList")]; const index = draft.findIndex((item) => item.planStpNo === planStpNo); diff --git a/apps/as/src/pages/sewer-bsc-plan/ui/sections/tmplnt-form-section.tsx b/apps/as/src/pages/sewer-bsc-plan/ui/sections/tmplnt-form-section.tsx index 8ee14fab2..bee7a8720 100644 --- a/apps/as/src/pages/sewer-bsc-plan/ui/sections/tmplnt-form-section.tsx +++ b/apps/as/src/pages/sewer-bsc-plan/ui/sections/tmplnt-form-section.tsx @@ -8,7 +8,7 @@ import { useConfirm } from "@frontend-opensource/use-react-hooks"; import { getDefaultFileKey, NON_NEGATIVE_INPUT_PROPS, - stripMinusSign, + sanitizeNumberInput, useTus, } from "@/shared/lib"; import { TextShimmer } from "@/shared/ui"; @@ -107,7 +107,7 @@ function TmplntDtlInput({ facilityIndex, planStpNo, fieldKey, decimals }: Tmplnt className="text-right" value={field.value ?? ""} onChange={(e) => { - const value = stripMinusSign(e.target.value.replace(/,/g, "")); + const value = sanitizeNumberInput(e.target.value.replace(/,/g, "")); const normalized = hasDecimals ? truncateDecimals(value, decimals) : value; field.onChange(Number(normalized) || 0); }} diff --git a/apps/as/src/shared/lib/index.ts b/apps/as/src/shared/lib/index.ts index b344b56bd..4735424af 100644 --- a/apps/as/src/shared/lib/index.ts +++ b/apps/as/src/shared/lib/index.ts @@ -2,7 +2,7 @@ export { getCookie, removeCookie, setCookie } from "./cookie"; export { dayjs } from "./dayjs"; export { getListHref, withListQs } from "./list-return-href"; export { matchQueryStatus } from "./match-query-status"; -export { NON_NEGATIVE_INPUT_PROPS, stripMinusSign } from "./number-input"; +export { NON_NEGATIVE_INPUT_PROPS, sanitizeNumberInput } from "./number-input"; export { queryClient } from "./query-client"; export { useAlertStore } from "./use-alert-store"; export { useDataTable } from "./use-data-table"; diff --git a/apps/as/src/shared/lib/number-input.ts b/apps/as/src/shared/lib/number-input.ts index 228f6948b..ac65664b2 100644 --- a/apps/as/src/shared/lib/number-input.ts +++ b/apps/as/src/shared/lib/number-input.ts @@ -1,5 +1,7 @@ import type { KeyboardEvent } from "react"; +const MAX_INTEGER_DIGITS = 10; + function preventMinusKey(e: KeyboardEvent) { if (e.key === "-") e.preventDefault(); } @@ -7,7 +9,14 @@ function preventMinusKey(e: KeyboardEvent) { /** type="number" Input에 스프레드해 음수 입력(키·스피너)을 막는다 */ export const NON_NEGATIVE_INPUT_PROPS = { min: 0, onKeyDown: preventMinusKey } as const; -/** 붙여넣기 등으로 들어온 음수 부호를 제거한다 */ -export function stripMinusSign(value: string): string { - return value.replace(/-/g, ""); +/** 정수부를 maxDigits 자리까지 잘라낸다 (소수부는 유지) */ +function truncateIntegerDigits(value: string, maxDigits: number): string { + const dotIndex = value.indexOf("."); + if (dotIndex < 0) return value.slice(0, maxDigits); + return value.slice(0, Math.min(dotIndex, maxDigits)) + value.slice(dotIndex); +} + +/** 숫자 입력 문자열에서 음수 부호를 제거하고 정수부를 최대 10자리로 제한한다 */ +export function sanitizeNumberInput(value: string): string { + return truncateIntegerDigits(value.replace(/-/g, ""), MAX_INTEGER_DIGITS); } 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 fb27309aa..8f32111c4 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 @@ -3,7 +3,7 @@ import { useEffect, useMemo } from "react"; import { useFormContext, useWatch } from "react-hook-form"; -import { NON_NEGATIVE_INPUT_PROPS, stripMinusSign } from "@/shared/lib"; +import { NON_NEGATIVE_INPUT_PROPS, sanitizeNumberInput } from "@/shared/lib"; import { SectionCollapsible } from "@/shared/ui"; import { Field } from "@/shared/ui/field"; import { useAcntgSeMngTree } from "@/entities/acntg"; @@ -80,7 +80,7 @@ function BusinessCostFormSection() { }, [costItems, getValues, setValue]); function handleCostChange(index: number, fieldKey: CostFieldKey, value: string) { - const numValue = Number(truncateDecimals(stripMinusSign(value), COST_DECIMAL_PLACES)) || 0; + const numValue = Number(truncateDecimals(sanitizeNumberInput(value), COST_DECIMAL_PLACES)) || 0; const draft = [...getValues("bizCstList")]; draft[index] = { ...draft[index], [fieldKey]: numValue }; const next = recalculateBizCstTotals(draft, costItems); 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 c7342b670..88e4dba0b 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 @@ -3,7 +3,7 @@ import { useEffect, useMemo } from "react"; import { useFormContext, useWatch } from "react-hook-form"; -import { NON_NEGATIVE_INPUT_PROPS, stripMinusSign } from "@/shared/lib"; +import { NON_NEGATIVE_INPUT_PROPS, sanitizeNumberInput } from "@/shared/lib"; import { SectionCollapsible } from "@/shared/ui"; import { Field } from "@/shared/ui/field"; import { useCnstpSeMngTree } from "@/entities/cnstp"; @@ -78,8 +78,8 @@ function BusinessScaleFormSection() { function handleScaleChange(index: number, fieldKey: ScaleFieldKey, value: string) { const item = scaleItems[index]; - const unsigned = stripMinusSign(value); - const normalized = item?.numYn ? truncateDecimals(unsigned, item.dcptUnitCnt) : unsigned; + const sanitized = sanitizeNumberInput(value); + const normalized = item?.numYn ? truncateDecimals(sanitized, item.dcptUnitCnt) : sanitized; const numValue = Number(normalized) || 0; const draft = [...getValues("bzScaleList")]; draft[index] = { ...draft[index], [fieldKey]: numValue }; @@ -101,6 +101,7 @@ function BusinessScaleFormSection() { return (
+

(단위: 백만원)

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 9604ac7fc..68a5019fa 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 @@ -6,7 +6,7 @@ import { useFormContext, useWatch } from "react-hook-form"; import { getDefaultFileKey, NON_NEGATIVE_INPUT_PROPS, - stripMinusSign, + sanitizeNumberInput, useTus, } from "@/shared/lib"; import { AttachmentFileField, SectionCollapsible } from "@/shared/ui"; @@ -147,7 +147,7 @@ function FnrsSharFormSection({ initialSharTree, initialSharAtchFiles }: FnrsShar if (fieldKey === "rmrkCn") { draft[index] = { ...row, rmrkCn: value }; } else { - const numValue = Number(stripMinusSign(value)) || 0; + const numValue = Number(sanitizeNumberInput(value)) || 0; draft[index] = { ...row, [fieldKey]: numValue }; }