UX improvements for mobile expense input

This commit is contained in:
Bruno Bernardino
2025-03-03 09:39:46 +00:00
parent 05c20ec0a2
commit df332802c0
4 changed files with 40 additions and 9 deletions

View File

@@ -2,6 +2,7 @@ import { useSignal } from '@preact/signals';
import { useEffect } from 'preact/hooks'; import { useEffect } from 'preact/hooks';
import { Budget } from '/lib/types.ts'; import { Budget } from '/lib/types.ts';
import { formatInputToNumber } from '/lib/utils/misc.ts';
interface BudgetModalProps { interface BudgetModalProps {
isOpen: boolean; isOpen: boolean;
@@ -17,7 +18,7 @@ export default function BudgetModal(
) { ) {
const newBudgetName = useSignal<string>(budget?.name ?? ''); const newBudgetName = useSignal<string>(budget?.name ?? '');
const newBudgetMonth = useSignal<string>(budget?.month ?? new Date().toISOString().substring(0, 10)); const newBudgetMonth = useSignal<string>(budget?.month ?? new Date().toISOString().substring(0, 10));
const newBudgetValue = useSignal<number>(budget?.value ?? 100); const newBudgetValue = useSignal<number | string>(budget?.value ?? 100);
const resetForm = () => { const resetForm = () => {
newBudgetName.value = ''; newBudgetName.value = '';
@@ -85,14 +86,13 @@ export default function BudgetModal(
<label class='text-slate-300 block pb-1' for='budget_value'>Value</label> <label class='text-slate-300 block pb-1' for='budget_value'>Value</label>
<input <input
class='input-field' class='input-field'
type='number' type='text'
name='budget_value' name='budget_value'
id='budget_value' id='budget_value'
value={newBudgetValue.value} value={newBudgetValue.value}
onInput={(event) => { onInput={(event) => {
newBudgetValue.value = Number(event.currentTarget.value); newBudgetValue.value = event.currentTarget.value;
}} }}
min='0'
inputmode='decimal' inputmode='decimal'
placeholder='100' placeholder='100'
/> />
@@ -117,7 +117,12 @@ export default function BudgetModal(
</button> </button>
<button <button
class='px-5 py-2 bg-slate-700 hover:bg-slate-500 text-white cursor-pointer rounded-md ml-2' class='px-5 py-2 bg-slate-700 hover:bg-slate-500 text-white cursor-pointer rounded-md ml-2'
onClick={() => onClickSave(newBudgetName.value, newBudgetMonth.value.substring(0, 7), newBudgetValue.value)} onClick={() =>
onClickSave(
newBudgetName.value,
newBudgetMonth.value.substring(0, 7),
formatInputToNumber(newBudgetValue.value),
)}
> >
{budget ? 'Update' : 'Create'} {budget ? 'Update' : 'Create'}
</button> </button>

View File

@@ -2,6 +2,7 @@ import { useSignal } from '@preact/signals';
import { useEffect } from 'preact/hooks'; import { useEffect } from 'preact/hooks';
import { Budget, Expense } from '/lib/types.ts'; import { Budget, Expense } from '/lib/types.ts';
import { formatInputToNumber } from '/lib/utils/misc.ts';
import { import {
RequestBody as SuggestionsRequestBody, RequestBody as SuggestionsRequestBody,
@@ -27,7 +28,7 @@ interface ExpenseModalProps {
export default function ExpenseModal( export default function ExpenseModal(
{ isOpen, expense, budgets, onClickSave, onClickDelete, onClose, shouldResetForm }: ExpenseModalProps, { isOpen, expense, budgets, onClickSave, onClickDelete, onClose, shouldResetForm }: ExpenseModalProps,
) { ) {
const newExpenseCost = useSignal<number | ''>(expense?.cost ?? ''); const newExpenseCost = useSignal<number | string>(expense?.cost ?? '');
const newExpenseDescription = useSignal<string>(expense?.description ?? ''); const newExpenseDescription = useSignal<string>(expense?.description ?? '');
const newExpenseBudget = useSignal<string>(expense?.budget ?? 'Misc'); const newExpenseBudget = useSignal<string>(expense?.budget ?? 'Misc');
const newExpenseDate = useSignal<string>(expense?.date ?? ''); const newExpenseDate = useSignal<string>(expense?.date ?? '');
@@ -112,12 +113,12 @@ export default function ExpenseModal(
<label class='text-slate-300 block pb-1' for='expense_cost'>Cost</label> <label class='text-slate-300 block pb-1' for='expense_cost'>Cost</label>
<input <input
class='input-field' class='input-field'
type='number' type='text'
name='expense_cost' name='expense_cost'
id='expense_cost' id='expense_cost'
value={newExpenseCost.value} value={newExpenseCost.value}
onInput={(event) => { onInput={(event) => {
newExpenseCost.value = Number(event.currentTarget.value); newExpenseCost.value = event.currentTarget.value;
}} }}
inputmode='decimal' inputmode='decimal'
placeholder='10.99' placeholder='10.99'
@@ -240,7 +241,7 @@ export default function ExpenseModal(
class='px-5 py-2 bg-slate-700 hover:bg-slate-500 text-white cursor-pointer rounded-md ml-2' class='px-5 py-2 bg-slate-700 hover:bg-slate-500 text-white cursor-pointer rounded-md ml-2'
onClick={() => { onClick={() => {
onClickSave( onClickSave(
newExpenseCost.value as number, formatInputToNumber(newExpenseCost.value),
newExpenseDescription.value, newExpenseDescription.value,
newExpenseBudget.value, newExpenseBudget.value,
newExpenseDate.value, newExpenseDate.value,

View File

@@ -268,3 +268,7 @@ export function formatNumber(currency: SupportedCurrencySymbol, number: number)
maximumFractionDigits: 2, maximumFractionDigits: 2,
}).format(Number.parseFloat(`${number}`.replace(',', '.'))); }).format(Number.parseFloat(`${number}`.replace(',', '.')));
} }
export function formatInputToNumber(numberInput: number | string): number {
return Number.parseFloat(`${numberInput}`.replace(',', '.'));
}

View File

@@ -5,6 +5,7 @@ import {
convertFormDataToObject, convertFormDataToObject,
convertObjectToFormData, convertObjectToFormData,
escapeHtml, escapeHtml,
formatInputToNumber,
formatNumber, formatNumber,
generateHash, generateHash,
generateRandomCode, generateRandomCode,
@@ -291,3 +292,23 @@ Deno.test('that formatNumber works', () => {
assertEquals(result, test.expected); assertEquals(result, test.expected);
} }
}); });
Deno.test('that formatInputToNumber works', () => {
const tests: { input: number | string; expected: number }[] = [
{ input: 42, expected: 42 },
{ input: '42', expected: 42 },
{ input: 42.5, expected: 42.5 },
{ input: '42.5', expected: 42.5 },
{ input: '42,5', expected: 42.5 },
{ input: '1234,56', expected: 1234.56 },
{ input: 0, expected: 0 },
{ input: '0', expected: 0 },
{ input: '0,0', expected: 0 },
{ input: '0.0', expected: 0 },
];
for (const test of tests) {
const result = formatInputToNumber(test.input);
assertEquals(result, test.expected);
}
});