Skip to content

Commit

Permalink
Move reports to TypeScript
Browse files Browse the repository at this point in the history
  • Loading branch information
ayastreb committed Nov 18, 2018
1 parent dfa21ed commit 00868f0
Show file tree
Hide file tree
Showing 24 changed files with 672 additions and 576 deletions.
2 changes: 2 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ REACT_APP_DEMO_DB_ACCOUNTS=https://couch.moneytracker.cc/accounts_2b4d1cad-7dcd-
REACT_APP_DEMO_DB_SETTINGS=https://couch.moneytracker.cc/settings_2b4d1cad-7dcd-42e6-b8fc-e29206a8f889
REACT_APP_DEMO_DB_TAGS=https://couch.moneytracker.cc/tags_2b4d1cad-7dcd-42e6-b8fc-e29206a8f889
REACT_APP_DEMO_DB_TRANSACTIONS=https://couch.moneytracker.cc/transactions_2b4d1cad-7dcd-42e6-b8fc-e29206a8f889

NODE_PATH=src
2 changes: 1 addition & 1 deletion src/components/Account/Form/BalanceTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class BalanceTable extends React.Component {
{this.props.form.id ? (
<Amount
code={code}
value={Currency.toInt(this.props.form.balance[code] || 0, code)}
value={Currency.toCents(this.props.form.balance[code] || 0, code)}
/>
) : (
<Input
Expand Down
4 changes: 2 additions & 2 deletions src/components/Account/ModalForm/DeleteStrategy.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import { Button, Radio, Dropdown, Form, Progress } from 'semantic-ui-react';
import {
defaultDeleteStrategy,
deleteStartegies,
deleteStartegyOptions,
DeleteStrategyT
} from '../../../entities/Account';

Expand Down Expand Up @@ -40,7 +40,7 @@ class DeleteStrategy extends React.Component {
What should we do with transactions linked to this account?
</p>
<Form>
{deleteStartegies(hasMultipleAccounts).map(strategy => (
{deleteStartegyOptions(hasMultipleAccounts).map(strategy => (
<Form.Field key={strategy.key}>
<Radio
name="deleteStrategy"
Expand Down
2 changes: 1 addition & 1 deletion src/components/Amount.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const Amount = ({ value, code, showColor = true }) => (
<span
className={`mono ${showColor && (value >= 0 ? 'positive' : 'negative')}`}
>
{Currency.toFloat(value, code)} {code}
{Currency.centsToString(value, code)} {code}
</span>
);

Expand Down
12 changes: 4 additions & 8 deletions src/components/Report/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Loader } from 'semantic-ui-react';
import {
REPORT_EXPENSE_INCOME,
REPORT_EXPENSE_TAGS,
REPORT_NET_WORTH
} from '../../entities/Report';
import { ReportKindT } from 'entities/Report';
import ExpenseIncome from './Kind/ExpenseIncome';
import ExpenseTags from './Kind/ExpenseTags';
import NetWorth from './Kind/NetWorth';
Expand All @@ -24,11 +20,11 @@ class Report extends React.Component {

renderReportByKind() {
switch (this.props.kind) {
case REPORT_EXPENSE_INCOME:
case ReportKindT.ExpenseIncome:
return <ExpenseIncome {...this.props} />;
case REPORT_NET_WORTH:
case ReportKindT.NetWorth:
return <NetWorth {...this.props} />;
case REPORT_EXPENSE_TAGS:
case ReportKindT.ExpenseTags:
return <ExpenseTags {...this.props} />;
default:
return 'Not available';
Expand Down
3 changes: 0 additions & 3 deletions src/entities/Account.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import {
AccountFormT,
AccountStateT,
AccountStorageT,
AccountGroupT,
defaultGroup,
groupOptions,
Expand Down
30 changes: 15 additions & 15 deletions src/entities/Account.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,36 @@
import Currency from './Currency';
import Currency from 'entities/Currency';

type AccountStateBalanceT = {
type BalanceAsCentsT = {
[currency: string]: number;
};

type AccountFormBalanceT = {
type BalanceAsInputStringT = {
[currency: string]: string;
};

interface AccountBaseT {
name: string;
group: AccountGroupT;
name: string;
currencies: string[];
on_dashboard: boolean;
archived?: boolean;
}

export interface AccountStateT extends AccountBaseT {
id: string;
balance: AccountStateBalanceT;
balance: BalanceAsCentsT;
}

export interface AccountStorageT extends AccountBaseT {
_id: string;
_rev?: string;
_conflicts?: string[];
balance: AccountStateBalanceT;
balance: BalanceAsCentsT;
}

export interface AccountFormT extends AccountBaseT {
id?: string;
balance: AccountFormBalanceT;
balance: BalanceAsInputStringT;
completed?: boolean;
isModalOpen?: boolean;
isDeleteRequest?: boolean;
Expand Down Expand Up @@ -79,7 +79,7 @@ export enum DeleteStrategyT {
Move
}
export const defaultDeleteStrategy = DeleteStrategyT.Archive;
export const deleteStartegies = (hasMultipleAccounts = false) => {
export const deleteStartegyOptions = (hasMultipleAccounts = false) => {
const stratgies = [
{
key: DeleteStrategyT.Archive,
Expand All @@ -105,27 +105,27 @@ export const deleteStartegies = (hasMultipleAccounts = false) => {

export const formTostate = ({
id,
balance,
name,
group,
balance,
currencies,
on_dashboard,
archived
}: AccountFormT): AccountStateT => {
return {
id: id || `A${Date.now()}`,
name,
group,
balance: Object.keys(balance).reduce(
(acc: AccountStateBalanceT, code: string) => {
acc[code] = Currency.toInt(
(acc: BalanceAsCentsT, code: string) => {
acc[code] = Currency.toCents(
balance[code] !== '' ? balance[code] : '0',
code
);
return acc;
},
{}
),
name,
group,
currencies,
on_dashboard,
archived
Expand All @@ -136,8 +136,8 @@ export const stateToForm = (account: AccountStateT): AccountFormT => {
return {
...account,
balance: Object.keys(account.balance).reduce(
(acc: AccountFormBalanceT, code: string) => {
acc[code] = Currency.toFloat(account.balance[code], code, false);
(acc: BalanceAsInputStringT, code: string) => {
acc[code] = Currency.centsToString(account.balance[code], code, false);
return acc;
},
{}
Expand Down
73 changes: 41 additions & 32 deletions src/entities/Currency.test.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,57 @@
import Currency from './Currency'
import Currency from './Currency';

it('has default base currency', () => {
expect(Currency.defaultBase).toEqual('USD')
})
expect(Currency.defaultBase).toEqual('USD');
});

it('returns list of options', () => {
const options = Currency.options()
const options = Currency.options();

expect(Array.isArray(options)).toBeTruthy()
expect(options.length).toBeGreaterThanOrEqual(1)
expect(options[0]).toHaveProperty('key')
expect(options[0]).toHaveProperty('value')
expect(options[0]).toHaveProperty('flag')
expect(options[0]).toHaveProperty('text')
expect(Array.isArray(options)).toBeTruthy();
expect(options.length).toBeGreaterThanOrEqual(1);
expect(options[0]).toHaveProperty('key');
expect(options[0]).toHaveProperty('value');
expect(options[0]).toHaveProperty('flag');
expect(options[0]).toHaveProperty('text');

const usdOption = options.find(option => option.key === 'USD')
expect(usdOption).toBeTruthy()
const usdOption = options.find(option => option.key === 'USD');
expect(usdOption).toBeTruthy();
if (usdOption) {
expect(usdOption.text).toEqual('USD, US Dollar')
expect(usdOption.flag).toEqual('us')
expect(usdOption.text).toEqual('USD, US Dollar');
expect(usdOption.flag).toEqual('us');
}
})
});

it('returns currency name', () => {
expect(Currency.name('USD')).toEqual('US Dollar')
})
expect(Currency.name('USD')).toEqual('US Dollar');
});

it('returns currency minimal amount', () => {
expect(Currency.minAmount('USD')).toEqual(0.01)
expect(Currency.minAmount('JPY')).toEqual(1)
expect(Currency.minAmount('KWD')).toEqual(0.001)
})
expect(Currency.minAmount('USD')).toEqual(0.01);
expect(Currency.minAmount('JPY')).toEqual(1);
expect(Currency.minAmount('KWD')).toEqual(0.001);
});

it('converts to currency subunit (cents)', () => {
expect(Currency.toInt(10.99, 'USD')).toEqual(1099) // $10.99 => 1099 cents
expect(Currency.toInt(9.5, 'USD')).toEqual(950) // $9.50 => 950 cents
expect(Currency.toInt(199, 'JPY')).toEqual(199) // 199 yen has no subunit, 1 yen is the minimum unit
expect(Currency.toInt(1.5, 'KWD')).toEqual(1500) // 1.5 Kuwaiti dinar => 1500 fils
})
expect(Currency.toCents(10.99, 'USD')).toEqual(1099); // $10.99 => 1099 cents
expect(Currency.toCents(9.5, 'USD')).toEqual(950); // $9.50 => 950 cents
expect(Currency.toCents(199, 'JPY')).toEqual(199); // 199 yen has no subunit, 1 yen is the minimum unit
expect(Currency.toCents(1.5, 'KWD')).toEqual(1500); // 1.5 Kuwaiti dinar => 1500 fils
});

it('converts from currency subunit (cents) back to float', () => {
expect(Currency.toFloat(1099, 'USD')).toEqual('10.99')
expect(Currency.toFloat(950, 'USD')).toEqual('9.50')
expect(Currency.toFloat(100099, 'USD')).toEqual('1,000.99')
expect(Currency.toFloat(199, 'JPY')).toEqual('199')
expect(Currency.toFloat(1550, 'KWD')).toEqual('1.550')
})
expect(Currency.centsToNumber(1099, 'USD')).toEqual(10.99);
expect(Currency.centsToNumber(950, 'USD')).toEqual(9.5);
expect(Currency.centsToNumber(100099, 'USD')).toEqual(1000.99);
expect(Currency.centsToNumber(199, 'JPY')).toEqual(199);
expect(Currency.centsToNumber(1550, 'KWD')).toEqual(1.55);
});

it('converts from currency subunit (cents) to human readable string', () => {
expect(Currency.centsToString(1099, 'USD')).toEqual('10.99');
expect(Currency.centsToString(950, 'USD')).toEqual('9.50');
expect(Currency.centsToString(100099, 'USD')).toEqual('1,000.99');
expect(Currency.centsToString(100099, 'USD', false)).toEqual('1000.99');
expect(Currency.centsToString(199, 'JPY')).toEqual('199');
expect(Currency.centsToString(1550, 'KWD')).toEqual('1.550');
});
15 changes: 13 additions & 2 deletions src/entities/Currency.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ const CURRENCY: AvailableCurrencyT = {
XAU: { name: 'Gold, troy ounce', symbol: 'XAU', exp: 2 }
};

export interface ExchangeRateT {
[code: string]: number;
}

type CurrencyOptionT = {
key: string;
value: string;
Expand Down Expand Up @@ -192,16 +196,23 @@ const Currency = {
* Convert value to currency's subunit (e.g. cents for USD).
* Subunit is the minimal currency unit and it is always whole integer.
*/
toInt(value: string | number, code: string) {
toCents(value: string | number, code: string) {
return Math.round(parseFloat(`${value}e${CURRENCY[code].exp}`));
},
/**
* Convert value from subunit back to float representation with formatting.
* For example 199001 USD becomes 1,990.01 USD
*/
toFloat(value: number, code: string, format: boolean = true): string {
centsToNumber(value: number, code: string): number {
const exp = CURRENCY[code].exp;
const num = Number(`${value}e-${exp}`);

return num;
},
centsToString(value: number, code: string, format: boolean = true): string {
const exp = CURRENCY[code].exp;
const num = Number(`${value}e-${exp}`);

return format
? num.toLocaleString(undefined, {
minimumFractionDigits: exp,
Expand Down
Loading

0 comments on commit 00868f0

Please sign in to comment.