UX improvements for mobile expense input
This commit is contained in:
@@ -2,6 +2,7 @@ import { useSignal } from '@preact/signals';
|
||||
import { useEffect } from 'preact/hooks';
|
||||
|
||||
import { Budget } from '/lib/types.ts';
|
||||
import { formatInputToNumber } from '/lib/utils/misc.ts';
|
||||
|
||||
interface BudgetModalProps {
|
||||
isOpen: boolean;
|
||||
@@ -17,7 +18,7 @@ export default function BudgetModal(
|
||||
) {
|
||||
const newBudgetName = useSignal<string>(budget?.name ?? '');
|
||||
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 = () => {
|
||||
newBudgetName.value = '';
|
||||
@@ -85,14 +86,13 @@ export default function BudgetModal(
|
||||
<label class='text-slate-300 block pb-1' for='budget_value'>Value</label>
|
||||
<input
|
||||
class='input-field'
|
||||
type='number'
|
||||
type='text'
|
||||
name='budget_value'
|
||||
id='budget_value'
|
||||
value={newBudgetValue.value}
|
||||
onInput={(event) => {
|
||||
newBudgetValue.value = Number(event.currentTarget.value);
|
||||
newBudgetValue.value = event.currentTarget.value;
|
||||
}}
|
||||
min='0'
|
||||
inputmode='decimal'
|
||||
placeholder='100'
|
||||
/>
|
||||
@@ -117,7 +117,12 @@ export default function BudgetModal(
|
||||
</button>
|
||||
<button
|
||||
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'}
|
||||
</button>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useSignal } from '@preact/signals';
|
||||
import { useEffect } from 'preact/hooks';
|
||||
|
||||
import { Budget, Expense } from '/lib/types.ts';
|
||||
import { formatInputToNumber } from '/lib/utils/misc.ts';
|
||||
|
||||
import {
|
||||
RequestBody as SuggestionsRequestBody,
|
||||
@@ -27,7 +28,7 @@ interface ExpenseModalProps {
|
||||
export default function ExpenseModal(
|
||||
{ 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 newExpenseBudget = useSignal<string>(expense?.budget ?? 'Misc');
|
||||
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>
|
||||
<input
|
||||
class='input-field'
|
||||
type='number'
|
||||
type='text'
|
||||
name='expense_cost'
|
||||
id='expense_cost'
|
||||
value={newExpenseCost.value}
|
||||
onInput={(event) => {
|
||||
newExpenseCost.value = Number(event.currentTarget.value);
|
||||
newExpenseCost.value = event.currentTarget.value;
|
||||
}}
|
||||
inputmode='decimal'
|
||||
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'
|
||||
onClick={() => {
|
||||
onClickSave(
|
||||
newExpenseCost.value as number,
|
||||
formatInputToNumber(newExpenseCost.value),
|
||||
newExpenseDescription.value,
|
||||
newExpenseBudget.value,
|
||||
newExpenseDate.value,
|
||||
|
||||
@@ -268,3 +268,7 @@ export function formatNumber(currency: SupportedCurrencySymbol, number: number)
|
||||
maximumFractionDigits: 2,
|
||||
}).format(Number.parseFloat(`${number}`.replace(',', '.')));
|
||||
}
|
||||
|
||||
export function formatInputToNumber(numberInput: number | string): number {
|
||||
return Number.parseFloat(`${numberInput}`.replace(',', '.'));
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
convertFormDataToObject,
|
||||
convertObjectToFormData,
|
||||
escapeHtml,
|
||||
formatInputToNumber,
|
||||
formatNumber,
|
||||
generateHash,
|
||||
generateRandomCode,
|
||||
@@ -291,3 +292,23 @@ Deno.test('that formatNumber works', () => {
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user