// @ts-strict-ignore import React, { type Ref, useRef, useState, useEffect, type FocusEventHandler, type KeyboardEvent, type CSSProperties, useCallback, } from 'react'; import { useTranslation } from 'react-i18next'; import { Button } from '@actual-app/components/button'; import { SvgAdd, SvgSubtract } from '@actual-app/components/icons/v1'; import { baseInputStyle, Input } from '@actual-app/components/input'; import { theme } from '@actual-app/components/theme'; import { View } from '@actual-app/components/view'; import { css, cx } from '@emotion/css'; import { useFormat } from '@desktop-client/hooks/useFormat'; import { useMergedRefs } from '@desktop-client/hooks/useMergedRefs'; type AmountInputProps = { id?: string; inputRef?: Ref; value: number; zeroSign?: '-' | '+'; sign?: '-' | '+'; onChangeValue?: (value: string) => void; onFocus?: FocusEventHandler; onBlur?: FocusEventHandler; onEnter?: (event: KeyboardEvent, amount?: number) => void; onUpdate?: (amount: number) => void; style?: CSSProperties; inputStyle?: CSSProperties; inputClassName?: string; focused?: boolean; disabled?: boolean; autoDecimals?: boolean; }; export function AmountInput({ id, inputRef, value: initialValue, zeroSign = '-', // + or - sign, onFocus, onBlur, onChangeValue, onUpdate, onEnter, style, inputStyle, inputClassName, focused, disabled = false, autoDecimals = false, }: AmountInputProps) { const { t } = useTranslation(); const format = useFormat(); const [symbol, setSymbol] = useState<'+' | '-'>(() => { if (sign) return sign; return initialValue === 0 ? zeroSign : initialValue > 0 ? '+' : '-'; }); const [isFocused, setIsFocused] = useState(focused ?? false); const getDisplayValue = useCallback( (value: number, isEditing: boolean) => { const absoluteValue = Math.abs(value || 0); return isEditing ? format.forEdit(absoluteValue) : format(absoluteValue, 'financial'); }, [format], ); const [value, setValue] = useState(getDisplayValue(initialValue, false)); useEffect( () => setValue(getDisplayValue(initialValue, isFocused)), [initialValue, isFocused, getDisplayValue], ); const buttonRef = useRef(null); const ref = useRef(null); const mergedRef = useMergedRefs(inputRef, ref); useEffect(() => { if (focused) { ref.current?.focus(); } }, [focused]); useEffect(() => { if (sign) { setSymbol(sign); } }, [sign]); const getAmount = useCallback(() => { const signedValued = symbol === '-' ? symbol + value : value; return format.fromEdit(signedValued, 0); }, [symbol, value, format]); useEffect(() => { if (ref.current) { ( ref.current as HTMLInputElement & { getCurrentAmount?: () => number } ).getCurrentAmount = () => getAmount(); } }, [getAmount]); function onSwitch() { if (sign) { return; } const amount = getAmount(); if (amount === 0) { setSymbol(symbol === '+' ? '-' : '+'); } fireUpdate(amount * -1); } function onInputTextChange(val) { let newText = val; if (autoDecimals) { const digits = val.replace(/\D/g, ''); if (digits === '') { newText = ''; } else { const intValue = parseInt(digits, 10); newText = format.forEdit(intValue); } } setValue(newText || ''); onChangeValue?.(newText); } function fireUpdate(amount) { onUpdate?.(amount); if (sign) { setSymbol(sign); } else { if (amount > 0) { setSymbol('+'); } else if (amount < 0) { setSymbol('-'); } } setValue(format(Math.abs(amount), 'financial')); } function onInputAmountBlur(e) { if (!ref.current?.contains(e.relatedTarget)) { const amount = getAmount(); fireUpdate(amount); } onBlur?.(e); } return ( { setIsFocused(true); setValue(format.forEdit(Math.abs(initialValue ?? 0))); onFocus?.(e); }} onBlur={e => { setIsFocused(false); onInputAmountBlur(e); }} onEnter={(_, e) => { const amount = getAmount(); fireUpdate(amount); onEnter?.(e, amount); }} onChangeValue={onInputTextChange} /> ); } type BetweenAmountInputProps = { defaultValue: { num1: number; num2: number }; onChange: (newValue: { num1: number; num2: number }) => void; }; export function BetweenAmountInput({ defaultValue, onChange, }: BetweenAmountInputProps) { const { t } = useTranslation(); const [num1, setNum1] = useState(defaultValue.num1); const [num2, setNum2] = useState(defaultValue.num2); return ( { setNum1(value); onChange({ num1: value, num2 }); }} style={{ color: theme.formInputText }} /> {t('and')} { setNum2(value); onChange({ num1, num2: value }); }} style={{ color: theme.formInputText }} /> ); }