From a6c5fe206014384aea7212f7dec189f84ff0d909 Mon Sep 17 00:00:00 2001 From: Igor Kamyshev Date: Tue, 19 Feb 2019 11:03:02 +0200 Subject: [PATCH] feat(money): add tables to history and simplify controls --- .../http/controller/HistoryController.ts | 16 +++++- .../features/app/features/history/History.css | 8 +-- .../features/app/features/history/History.tsx | 44 +++++++-------- .../features/history/organisms/Incomes.tsx | 42 +++++++++----- .../features/history/organisms/Outcomes.tsx | 42 +++++++++----- front/src/helpers/NON_BREAKING_SPACE.ts | 1 + front/src/helpers/displayMoney.ts | 13 +++++ front/src/helpers/displayNullableDtae.ts | 7 +++ front/src/ui/molecules/table/Table.tsx | 56 +++++++++++++++++++ front/src/ui/molecules/table/index.ts | 1 + 10 files changed, 169 insertions(+), 61 deletions(-) create mode 100644 front/src/helpers/NON_BREAKING_SPACE.ts create mode 100644 front/src/helpers/displayMoney.ts create mode 100644 front/src/helpers/displayNullableDtae.ts create mode 100644 front/src/ui/molecules/table/Table.tsx create mode 100644 front/src/ui/molecules/table/index.ts diff --git a/back/src/money/presentation/http/controller/HistoryController.ts b/back/src/money/presentation/http/controller/HistoryController.ts index 8c66d39..dc0a90a 100644 --- a/back/src/money/presentation/http/controller/HistoryController.ts +++ b/back/src/money/presentation/http/controller/HistoryController.ts @@ -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' @@ -41,8 +42,17 @@ export class HistoryController { ): Promise { 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)), + ), + ), ) } diff --git a/front/src/features/app/features/history/History.css b/front/src/features/app/features/history/History.css index 36d479d..293fdc1 100644 --- a/front/src/features/app/features/history/History.css +++ b/front/src/features/app/features/history/History.css @@ -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 { diff --git a/front/src/features/app/features/history/History.tsx b/front/src/features/app/features/history/History.tsx index 48a4da5..3e062fa 100644 --- a/front/src/features/app/features/history/History.tsx +++ b/front/src/features/app/features/history/History.tsx @@ -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' @@ -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 (

History

- {history.nonEmpty() && history.get().map(({ title, incomes, outcomes }) => (
-

{title}

- - + +
))}
diff --git a/front/src/features/app/features/history/organisms/Incomes.tsx b/front/src/features/app/features/history/organisms/Incomes.tsx index 252c17e..af259ec 100644 --- a/front/src/features/app/features/history/organisms/Incomes.tsx +++ b/front/src/features/app/features/history/organisms/Incomes.tsx @@ -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) => ( -
-

Incomes

- {incomes.map(({ amount, currency, source, date }) => ( -

- {Option.of(date) - .map(_ => _.toDateString()) - .getOrElse('')} - {' — '} - {amount / 100} {currency} ({source}) -

- ))} -
+export const Incomes = ({ incomes, periodName, className }: Props) => ( + ({ + ...income, + amount: displayMoney(income.amount, income.currency), + }))} + columns={columns} + /> ) diff --git a/front/src/features/app/features/history/organisms/Outcomes.tsx b/front/src/features/app/features/history/organisms/Outcomes.tsx index d515802..5ed8b63 100644 --- a/front/src/features/app/features/history/organisms/Outcomes.tsx +++ b/front/src/features/app/features/history/organisms/Outcomes.tsx @@ -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) => ( -
-

Outcomes

- {outcomes.map(({ amount, currency, category, date }) => ( -

- {Option.of(date) - .map(_ => _.toDateString()) - .getOrElse('')} - {' — '} - {amount / 100} {currency} ({category}) -

- ))} -
+export const Outcomes = ({ outcomes, periodName, className }: Props) => ( +
({ + ...outcome, + amount: displayMoney(outcome.amount, outcome.currency), + }))} + columns={columns} + /> ) diff --git a/front/src/helpers/NON_BREAKING_SPACE.ts b/front/src/helpers/NON_BREAKING_SPACE.ts new file mode 100644 index 0000000..dfe1fb5 --- /dev/null +++ b/front/src/helpers/NON_BREAKING_SPACE.ts @@ -0,0 +1 @@ +export const NON_BREAKING_SPACE = String.fromCharCode(160) diff --git a/front/src/helpers/displayMoney.ts b/front/src/helpers/displayMoney.ts new file mode 100644 index 0000000..bad96a8 --- /dev/null +++ b/front/src/helpers/displayMoney.ts @@ -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}` +} diff --git a/front/src/helpers/displayNullableDtae.ts b/front/src/helpers/displayNullableDtae.ts new file mode 100644 index 0000000..1d6a309 --- /dev/null +++ b/front/src/helpers/displayNullableDtae.ts @@ -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('') diff --git a/front/src/ui/molecules/table/Table.tsx b/front/src/ui/molecules/table/Table.tsx new file mode 100644 index 0000000..9380067 --- /dev/null +++ b/front/src/ui/molecules/table/Table.tsx @@ -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: Data + columns: Columns + className?: string + title: string +} + +export const Table = >({ + className, + data, + columns, + title, +}: Props) => { + 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 ( + title} + /> + ) +} diff --git a/front/src/ui/molecules/table/index.ts b/front/src/ui/molecules/table/index.ts new file mode 100644 index 0000000..3be5c81 --- /dev/null +++ b/front/src/ui/molecules/table/index.ts @@ -0,0 +1 @@ +export { Table } from './Table'