diff --git a/src/components/input/NumberInput.tsx b/src/components/input/NumberInput.tsx index 132688d7..89b02845 100644 --- a/src/components/input/NumberInput.tsx +++ b/src/components/input/NumberInput.tsx @@ -1,413 +1,58 @@ -'use client'; +"use client"; -import { - ChangeEvent, - ChangeEventHandler, - FocusEventHandler, - ReactNode, - useEffect, - useRef, - useState, -} from 'react'; +import { ChangeEvent, ReactNode } from "react"; +import { NumericFormat, OnValueChange } from "react-number-format"; +import TextInput, { TextInputProps } from "@/components/input/TextInput"; -import { cn } from '@/lib/helper'; -import Inputmask from 'inputmask'; - -const createInputMask = ( - maskType: MaskType, - decimals: number, - thousandSeparator: string, - decimalSeparator: string, - allowNegative: boolean, - oncomplete?: () => void, - onincomplete?: () => void, - oncleared?: () => void -): Inputmask.Instance => { - const options: Inputmask.Options = { - alias: 'numeric', - groupSeparator: thousandSeparator, - radixPoint: decimalSeparator, - digits: decimals, - allowMinus: allowNegative, - rightAlign: false, - insertMode: true, - autoUnmask: false, - clearMaskOnLostFocus: false, - digitsOptional: decimals > 0, - placeholder: '0', - numericInput: false, - positionCaretOnClick: 'radixFocus', - greedy: true, - oncomplete, - onincomplete, - oncleared - }; - - return new Inputmask(options); -}; - -export type MaskType = 'currency' | 'weight' | 'decimal' | 'number' | 'text'; - -export interface NumberInputProps { - label?: string; - bottomLabel?: string; - name: string; - value?: number | string; - placeholder?: string; - - className?: { - wrapper?: string; - label?: string; - inputWrapper?: string; - input?: string; - }; - - isError?: boolean; - isValid?: boolean; - errorMessage?: string; - disabled?: boolean; - readOnly?: boolean; - required?: boolean; - isLoading?: boolean; - - startAdornment?: ReactNode; - endAdornment?: ReactNode; - - onChange?: ChangeEventHandler; - onBlur?: FocusEventHandler; - onFocus?: FocusEventHandler; - - maskType?: MaskType; - decimals?: number; +interface NumberInputProps extends Omit { thousandSeparator?: string; decimalSeparator?: string; - currencyPrefix?: string; - weightUnit?: string; - - min?: number; - max?: number; + decimalScale?: number; allowNegative?: boolean; - - oncomplete?: () => void; - onincomplete?: () => void; - oncleared?: () => void; + prefix?: string; + suffix?: string; + fixedDecimalScale?: boolean; + inputPrefix?: ReactNode; + inputSuffix?: ReactNode; } const NumberInput = ({ - label, - bottomLabel, - name, - value, - placeholder, - className, - isError, - isValid, - errorMessage, - startAdornment, - endAdornment, - disabled = false, - required = false, - onChange, - onBlur, - onFocus, - readOnly = false, - isLoading = false, - maskType = 'number', - decimals = 0, - thousandSeparator = ',', - decimalSeparator = '.', - currencyPrefix = 'Rp ', - weightUnit = 'kg', - allowNegative = false, - oncomplete, - onincomplete, - oncleared, - }: NumberInputProps) => { - const inputRef = useRef(null); - const inputmaskRef = useRef(null); - const [maskComplete, setMaskComplete] = useState(false); - const [maskIncomplete, setMaskIncomplete] = useState(false); - const [maskCleared, setMaskCleared] = useState(false); + thousandSeparator = ",", + decimalSeparator = ".", + decimalScale = 5, + allowNegative = true, + onChange, + inputPrefix, + inputSuffix, + ...restProps +}: NumberInputProps) => { + const valueChangeHandler: OnValueChange = ( + numberFormatValues, + sourceInfo, + ) => { + const newChangeEvent = sourceInfo.event as + | ChangeEvent + | undefined; - const getInputPrefix = (): string => { - switch (maskType) { - case 'currency': - return currencyPrefix; - default: - return ''; + if (newChangeEvent) { + newChangeEvent.target.value = numberFormatValues.value; + + onChange?.(newChangeEvent); } }; - const getInputSuffix = (): string => { - switch (maskType) { - case 'weight': - return weightUnit; - default: - return ''; - } - }; - - useEffect(() => { - if (inputRef.current && !readOnly && !disabled) { - if (inputmaskRef.current) { - try { - inputmaskRef.current.remove(); - } catch (error) { - console.warn('Error removing Inputmask:', error); - } - } - - const handleComplete = () => { - setMaskComplete(true); - setMaskIncomplete(false); - setMaskCleared(false); - if (oncomplete) oncomplete(); - }; - - const handleIncomplete = () => { - setMaskIncomplete(true); - setMaskComplete(false); - setMaskCleared(false); - if (onincomplete) onincomplete(); - }; - - const handleCleared = () => { - setMaskCleared(true); - setMaskComplete(false); - setMaskIncomplete(false); - if (oncleared) oncleared(); - }; - - const im = createInputMask( - maskType, - decimals, - ',', - '.', - allowNegative, - handleComplete, - handleIncomplete, - handleCleared - ); - - try { - im.mask(inputRef.current); - inputmaskRef.current = im; - } catch (error) { - console.warn('Error applying Inputmask:', error); - inputmaskRef.current = null; - } - } - - return () => { - if (inputmaskRef.current) { - try { - inputmaskRef.current.remove(); - } catch (error) { - console.warn('Error removing Inputmask on cleanup:', error); - } - } - }; - }, [maskType, decimals, thousandSeparator, decimalSeparator, allowNegative, readOnly, disabled, oncomplete, onincomplete, oncleared]); - - useEffect(() => { - if (inputRef.current && value !== undefined) { - if (value === null || value === '') { - inputRef.current.value = ''; - } else { - inputRef.current.value = String(value); - } - } - }, [value]); - - const handleKeyUp = (e: React.KeyboardEvent) => { - const currentValue = (e.currentTarget as HTMLInputElement).value; - console.log('✅ After format:', currentValue); - - if (onChange) { - const syntheticEvent = { - target: { - name, - value: currentValue, - }, - } as ChangeEvent; - onChange(syntheticEvent); - } - }; - - const inputPrefix = getInputPrefix(); - const inputSuffix = getInputSuffix(); - return ( -
- {label && ( - - )} - -
- {inputPrefix && ( -
- - {inputPrefix} - -
- )} - -
- {startAdornment && startAdornment} - - - - {(isLoading || endAdornment) && ( -
- {isLoading && } - {endAdornment && endAdornment} -
- )} -
- - {inputSuffix && ( -
- - {inputSuffix} - -
- )} -
- - {(maskType === 'text' || (oncomplete || onincomplete || oncleared)) && ( -
- - Complete - - - Incomplete - - - Cleared - -
- )} - - {!isError && bottomLabel && ( -

{bottomLabel}

- )} - {isError && errorMessage && ( -

{errorMessage}

- )} -
+ ); }; diff --git a/src/components/pages/inventory/movement/form/MovementForm.tsx b/src/components/pages/inventory/movement/form/MovementForm.tsx index d2c91168..d8239d14 100644 --- a/src/components/pages/inventory/movement/form/MovementForm.tsx +++ b/src/components/pages/inventory/movement/form/MovementForm.tsx @@ -1377,9 +1377,6 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { handleDeliveryCostChange(idx, e.target.value) } onBlur={formik.handleBlur} - maskType='currency' - decimals={0} - min={0} {...isRepeaterInputError( 'deliveries', 'delivery_cost', @@ -1404,9 +1401,6 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { ) } onBlur={formik.handleBlur} - maskType='currency' - decimals={0} - min={0} {...isRepeaterInputError( 'deliveries', 'delivery_cost_per_item', diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index 4d8ef9ca..6018ae84 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -942,9 +942,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { value={feed.feed_stock} onChange={handleFeedStockChangeWrapper(idx)} onBlur={formik.handleBlur} - maskType='number' - decimals={0} - min={0} thousandSeparator=',' decimalSeparator='' isError={ @@ -1120,10 +1117,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { value={weight.chicken_weight} onChange={handleChickenWeightChangeWrapper(idx)} onBlur={formik.handleBlur} - maskType='weight' - weightUnit='gram' - decimals={2} - min={0} thousandSeparator=',' decimalSeparator='.' isError={ @@ -1153,9 +1146,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { value={weight.chicken_count} onChange={handleChickenCountChangeWrapper(idx)} onBlur={formik.handleBlur} - maskType='number' - decimals={0} - min={0} isError={ isRepeaterInputError( 'body_weight', @@ -1184,10 +1174,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { value={weight.average_chicken_weight || ''} onChange={handleAverageWeightChangeWrapper(idx)} onBlur={formik.handleBlur} - maskType='weight' - weightUnit='gram' - decimals={2} - min={0} thousandSeparator=',' decimalSeparator='.' isError={ @@ -1450,9 +1436,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { value={vaccine.used_stock} onChange={handleVaccinationStockChangeWrapper(idx)} onBlur={formik.handleBlur} - maskType='number' - decimals={0} - min={0} thousandSeparator=',' decimalSeparator='' isError={ @@ -1660,9 +1643,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { value={mortality.count} onChange={handleMortalityCountChangeWrapper(idx)} onBlur={formik.handleBlur} - maskType='number' - decimals={0} - min={0} thousandSeparator=',' decimalSeparator='' isError={