'use client'; import { ChangeEvent, ChangeEventHandler, FocusEventHandler, ReactNode, useEffect, useRef, useState, } from 'react'; 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; thousandSeparator?: string; decimalSeparator?: string; currencyPrefix?: string; weightUnit?: string; min?: number; max?: number; allowNegative?: boolean; oncomplete?: () => void; onincomplete?: () => void; oncleared?: () => void; } 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); const getInputPrefix = (): string => { switch (maskType) { case 'currency': return currencyPrefix; default: return ''; } }; 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}

)}
); }; export default NumberInput;