From 7deacd2a983ba1f7a541531bcd674f2e8bc92b7e Mon Sep 17 00:00:00 2001 From: ZProger Date: Sun, 30 Aug 2026 05:10:29 +0300 Subject: [PATCH] fix: quantity/price fields padded with zeros when editing a transaction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The API returns quantity and price_per_unit at their full Numeric(38,18) scale (e.g. "3.000000000000000000") since crypto quantities need up to 18 decimal places for wei-level amounts — correct for computation, but the edit form pre-filled these raw strings straight into the inputs. Added trimTrailingZeros() to lib/format.ts and used it when pre-filling CryptoTransactionModal in edit mode. Trims the string directly rather than round-tripping through Number(), which would risk rounding a genuine 18-decimal-place amount through floating point instead of just stripping padding. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01855PiW5Wd8n4aRW4n9kByY --- .../components/crypto/CryptoTransactionModal.tsx | 8 ++++++-- frontend/src/lib/format.ts | 14 ++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/crypto/CryptoTransactionModal.tsx b/frontend/src/components/crypto/CryptoTransactionModal.tsx index 74e7ef2..f09d228 100644 --- a/frontend/src/components/crypto/CryptoTransactionModal.tsx +++ b/frontend/src/components/crypto/CryptoTransactionModal.tsx @@ -4,6 +4,7 @@ import { Button } from "@/components/ui/Button"; import { Dialog } from "@/components/ui/Dialog"; import { Input, Label } from "@/components/ui/Input"; import { useAddCryptoTransaction, useUpdateCryptoTransaction } from "@/hooks/useCrypto"; +import { trimTrailingZeros } from "@/lib/format"; import { useTranslation } from "@/lib/i18n"; import { cn } from "@/lib/utils"; import type { CryptoHolding, CryptoTransaction, CryptoTransactionType } from "@/types"; @@ -39,8 +40,11 @@ export function CryptoTransactionModal({ open, onClose, holding, transaction = n if (!open) return; if (transaction) { setType(transaction.type); - setQuantity(transaction.quantity); - setPricePerUnit(transaction.price_per_unit); + // The API returns these at full Numeric(38,18) scale (e.g. + // "3.000000000000000000") — trimmed here so the field shows what was + // actually entered, not a wall of padding zeros. + setQuantity(trimTrailingZeros(transaction.quantity)); + setPricePerUnit(trimTrailingZeros(transaction.price_per_unit)); setDate(transaction.date); setNote(transaction.note ?? ""); } else { diff --git a/frontend/src/lib/format.ts b/frontend/src/lib/format.ts index 666e95b..5db7013 100644 --- a/frontend/src/lib/format.ts +++ b/frontend/src/lib/format.ts @@ -32,6 +32,20 @@ export function maskAmount(formatted: string, hidden: boolean): string { return hidden ? "••••" : formatted; } +/** Strips trailing zeros from a decimal string for pre-filling an editable + * number input — the backend stores crypto quantity/price as Numeric(38,18) + * and returns it at full scale (e.g. "3.000000000000000000"), which is + * correct for computation but not something anyone wants to see or edit + * around in a form field. Works on the string directly rather than via + * Number(), which would round a genuine 18-decimal-place amount (a + * wei-level token quantity) through floating point instead of just + * trimming the padding. */ +export function trimTrailingZeros(value: string): string { + if (!value.includes(".")) return value; + const trimmed = value.replace(/0+$/, "").replace(/\.$/, ""); + return trimmed === "" || trimmed === "-" ? "0" : trimmed; +} + const MONTH_LABELS_EN = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] as const; const MONTH_LABELS_RU = ["Янв", "Фев", "Мар", "Апр", "Май", "Июн", "Июл", "Авг", "Сен", "Окт", "Ноя", "Дек"] as const;