[as] chore: 행정지원 숫자 폼 최대자릿수 제한

This commit is contained in:
2026-10-02 16:37:26 +09:00
parent 85f535eb5d
commit d6522338aa
9 changed files with 30 additions and 20 deletions
@@ -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),
@@ -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;
@@ -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);
@@ -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);
}}
+1 -1
View File
@@ -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";
+12 -3
View File
@@ -1,5 +1,7 @@
import type { KeyboardEvent } from "react";
const MAX_INTEGER_DIGITS = 10;
function preventMinusKey(e: KeyboardEvent<HTMLInputElement>) {
if (e.key === "-") e.preventDefault();
}
@@ -7,7 +9,14 @@ function preventMinusKey(e: KeyboardEvent<HTMLInputElement>) {
/** 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);
}
@@ -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);
@@ -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 (
<SectionCollapsible title="사업규모">
<div className="overflow-x-auto">
<p className="mb-2 text-right font-normal text-muted-foreground">(단위: 백만원)</p>
<Table variant="secondary">
<colgroup>
<col width="20%" />
@@ -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 };
}