Skip to content
This repository has been archived by the owner on May 11, 2021. It is now read-only.

Commit

Permalink
feat(money): add tables to history and simplify controls
Browse files Browse the repository at this point in the history
  • Loading branch information
igorkamyshev committed Feb 19, 2019
1 parent 902396b commit a6c5fe2
Show file tree
Hide file tree
Showing 10 changed files with 169 additions and 61 deletions.
16 changes: 13 additions & 3 deletions back/src/money/presentation/http/controller/HistoryController.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { Controller, Get, Query } from '@nestjs/common'
import {
ApiBearerAuth,
ApiImplicitQuery,
ApiOkResponse,
ApiOperation,
ApiUseTags,
} from '@nestjs/swagger'
import { reverse, sortBy } from 'lodash'

import { Historian } from '@back/money/application/Historian'
import { AbstractTransaction } from '@back/money/domain/dto/AbstarctTransaction'
import { TokenPayload } from '@back/user/application/dto/TokenPayload'
import { CurrentUser } from '@back/user/presentation/http/decorator/CurrentUser'
import { OnlyForUsers } from '@back/user/presentation/http/security/OnlyForUsers'
Expand Down Expand Up @@ -41,8 +42,17 @@ export class HistoryController {
): Promise<HistoryGroupResponse[]> {
const history = await this.historian.showGroupedHistory(login, range, by)

return history.map(({ title, incomes, outcomes }) =>
HistoryGroupResponse.fromPair(title, incomes, outcomes),
const sorter = (transaction: AbstractTransaction) =>
-transaction.date.valueOf()

return reverse(
history.map(({ title, incomes, outcomes }) =>
HistoryGroupResponse.fromPair(
title,
sortBy(incomes, income => sorter(income)),
sortBy(outcomes, outcome => sorter(outcome)),
),
),
)
}

Expand Down
8 changes: 2 additions & 6 deletions front/src/features/app/features/history/History.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,18 @@
display: grid;

grid-template:
'title title '
'outcomes incomes'
/ 1fr 1fr;

@media (max-width: 576px) {
grid-template:
'title'
'outcomes'
'incomes';
}

gap: 16px;
}

.title {
grid-area: title;
padding-top: 8px;
padding-bottom: 8px;
}

.outcomes {
Expand Down
44 changes: 22 additions & 22 deletions front/src/features/app/features/history/History.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { max, startOfMonth } from 'date-fns'
import { endOfMonth, startOfMonth } from 'date-fns'
import { useCallback, useEffect, useState } from 'react'
import { useDispatch, useMappedState } from 'redux-react-hook'

import { fetchHistory } from '@front/domain/money/actions/fetchHistory'
import { getFirstTransactionDate } from '@front/domain/money/selectors/getFirstTransactionDate'
import { getHistory } from '@front/domain/money/selectors/getHistory'
import { getHistoryFetchingStatus } from '@front/domain/money/selectors/getHistoryFetchingStatus'
import { Loader } from '@front/ui/molecules/loader'
import { Groupment } from '@front/ui/organisms/groupment'
import { Period } from '@front/ui/organisms/period'
import { GroupBy } from '@shared/enum/GroupBy'

Expand All @@ -19,41 +17,43 @@ interface Props {
className?: string
}

const groupBy = GroupBy.Month

export const History = ({ className }: Props) => {
const firstTransactionDate = useMappedState(getFirstTransactionDate)
const fetching = useMappedState(getHistoryFetchingStatus)
const dispatch = useDispatch()

const [from, setFrom] = useState(
max(firstTransactionDate, startOfMonth(new Date())),
)
const [to, setTo] = useState(new Date())
const [groupBy, setGroupBy] = useState(GroupBy.Month)

const updateTriggers = [from, to, groupBy]
const [from, setFrom] = useState(startOfMonth(new Date()))
const [to, setTo] = useState(endOfMonth(new Date()))

const historySelector = useCallback(
getHistory(from, to, groupBy),
updateTriggers,
)
const historySelector = useCallback(getHistory(from, to, groupBy), [from, to])
const history = useMappedState(historySelector)

useEffect(() => {
dispatch(fetchHistory(from, to, groupBy) as any)
}, updateTriggers)
useEffect(
() => {
dispatch(fetchHistory(from, to, groupBy) as any)
},
[from, to],
)

return (
<section className={className}>
<h2>History</h2>
<Groupment groupBy={groupBy} updateGroupBy={setGroupBy} />
<Period start={from} updateStart={setFrom} end={to} updateEnd={setTo} />
<Loader status={fetching}>
{history.nonEmpty() &&
history.get().map(({ title, incomes, outcomes }) => (
<div key={title} className={styles.dataSet}>
<h3 className={styles.title}>{title}</h3>
<Outcomes outcomes={outcomes} className={styles.outcomes} />
<Incomes incomes={incomes} className={styles.incomes} />
<Outcomes
outcomes={outcomes}
periodName={title}
className={styles.outcomes}
/>
<Incomes
incomes={incomes}
periodName={title}
className={styles.incomes}
/>
</div>
))}
</Loader>
Expand Down
42 changes: 27 additions & 15 deletions front/src/features/app/features/history/organisms/Incomes.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,35 @@
import { Option } from 'tsoption'

import { displayMoney } from '@front/helpers/displayMoney'
import { displayNullableDate } from '@front/helpers/displayNullableDtae'
import { Table } from '@front/ui/molecules/table'
import { IncomeModel } from '@shared/models/money/IncomeModel'

interface Props {
incomes: IncomeModel[]
className?: string
periodName: string
}

const columns = {
date: {
title: 'Date',
transform: displayNullableDate,
},
amount: {
title: 'Amount',
},
source: {
title: 'Source',
},
}

export const Incomes = ({ incomes, className }: Props) => (
<section className={className}>
<h4>Incomes</h4>
{incomes.map(({ amount, currency, source, date }) => (
<p key={`${amount}-${date}`}>
{Option.of(date)
.map(_ => _.toDateString())
.getOrElse('')}
{' — '}
{amount / 100} {currency} ({source})
</p>
))}
</section>
export const Incomes = ({ incomes, periodName, className }: Props) => (
<Table
title={`Incomes: ${periodName}`}
className={className}
data={incomes.map(income => ({
...income,
amount: displayMoney(income.amount, income.currency),
}))}
columns={columns}
/>
)
42 changes: 27 additions & 15 deletions front/src/features/app/features/history/organisms/Outcomes.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,35 @@
import { Option } from 'tsoption'

import { displayMoney } from '@front/helpers/displayMoney'
import { displayNullableDate } from '@front/helpers/displayNullableDtae'
import { Table } from '@front/ui/molecules/table'
import { OutcomeModel } from '@shared/models/money/OutcomeModel'

interface Props {
outcomes: OutcomeModel[]
className?: string
periodName: string
}

const columns = {
date: {
title: 'Date',
transform: displayNullableDate,
},
amount: {
title: 'Amount',
},
category: {
title: 'Category',
},
}

export const Outcomes = ({ outcomes, className }: Props) => (
<section className={className}>
<h4>Outcomes</h4>
{outcomes.map(({ amount, currency, category, date }) => (
<p key={`${amount}-${date}`}>
{Option.of(date)
.map(_ => _.toDateString())
.getOrElse('')}
{' — '}
{amount / 100} {currency} ({category})
</p>
))}
</section>
export const Outcomes = ({ outcomes, periodName, className }: Props) => (
<Table
title={`Outcomes: ${periodName}`}
className={className}
data={outcomes.map(outcome => ({
...outcome,
amount: displayMoney(outcome.amount, outcome.currency),
}))}
columns={columns}
/>
)
1 change: 1 addition & 0 deletions front/src/helpers/NON_BREAKING_SPACE.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const NON_BREAKING_SPACE = String.fromCharCode(160)
13 changes: 13 additions & 0 deletions front/src/helpers/displayMoney.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Currency } from '@shared/enum/Currency'

import { getCurrencySign } from './getCurrencySign'
import { NON_BREAKING_SPACE } from './NON_BREAKING_SPACE'

export const displayMoney = (amount: number, currency: Currency) => {
const formattedAmount = (amount / 100)
.toFixed(2)
.toString()
.replace(/\B(?=(\d{3})+(?!\d))/g, NON_BREAKING_SPACE)

return `${getCurrencySign(currency)}${NON_BREAKING_SPACE}${formattedAmount}`
}
7 changes: 7 additions & 0 deletions front/src/helpers/displayNullableDtae.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { format } from 'date-fns'
import { Option } from 'tsoption'

export const displayNullableDate = (date?: Date) =>
Option.of(date)
.map(d => format(d, 'D.MM.YYYY'))
.getOrElse('')
56 changes: 56 additions & 0 deletions front/src/ui/molecules/table/Table.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Table as AntTable } from 'antd'
import { ReactNode, useMemo } from 'react'

interface Column {
title: string
transform?: (v: any) => ReactNode
}

interface Columns {
[key: string]: Column
}

interface Props<Data extends Array<{}>> {
data: Data
columns: Columns
className?: string
title: string
}

export const Table = <Data extends Array<{}>>({
className,
data,
columns,
title,
}: Props<Data>) => {
const adoptedData = useMemo(
() =>
data.map((dataItem, key) => ({
key,
...dataItem,
})),
[data],
)

const adoptedColumns = useMemo(
() =>
Object.entries(columns).map(([key, value]) => ({
...value,
dataIndex: key,
render: value.transform,
})),
[columns],
)

return (
<AntTable
dataSource={adoptedData}
columns={adoptedColumns}
className={className}
bordered
size="middle"
pagination={false}
title={() => title}
/>
)
}
1 change: 1 addition & 0 deletions front/src/ui/molecules/table/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Table } from './Table'

0 comments on commit a6c5fe2

Please sign in to comment.