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 history contolling on front
Browse files Browse the repository at this point in the history
  • Loading branch information
igorkamyshev committed Feb 14, 2019
1 parent 2bb14c5 commit 29ea988
Show file tree
Hide file tree
Showing 11 changed files with 168 additions and 23 deletions.
12 changes: 10 additions & 2 deletions front/src/domain/money/actions/createIncome.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,21 @@ import { fetchOrFail } from '@front/domain/store/fetchingRedux/fetchOrFail'

import { createIncomeRequest } from '../api/createIncomeRequest'
import { actions as incomeFetchingActions } from '../reducer/createIncomeFetching'
import { getHistoryCachedPeriods } from '../selectors/getHistoryCachedPeriods'
import { fetchFirstTransactionDate } from './fetchFirstTransactionDate'
import { forceFetchHistory } from './forceFetchHistory'

export const createIncome = (incomeFields: IncomeModel) =>
fetchOrFail(incomeFetchingActions, async (dispatch, getApi) => {
fetchOrFail(incomeFetchingActions, async (dispatch, getApi, getState) => {
await createIncomeRequest(getApi())(incomeFields)

await dispatch(fetchFirstTransactionDate() as any)

// TODO: refetch history
const historyCachedPerios = getHistoryCachedPeriods(getState())

await Promise.all(
historyCachedPerios.map(({ from, to, groupBy }) =>
dispatch(forceFetchHistory(from, to, groupBy) as any),
),
)
})
12 changes: 10 additions & 2 deletions front/src/domain/money/actions/createOutcome.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,21 @@ import { fetchOrFail } from '@front/domain/store/fetchingRedux/fetchOrFail'

import { createOutcomeRequest } from '../api/createOutcomeRequest'
import { actions as outcomeFetchingActions } from '../reducer/createOutcomeFetching'
import { getHistoryCachedPeriods } from '../selectors/getHistoryCachedPeriods'
import { fetchFirstTransactionDate } from './fetchFirstTransactionDate'
import { forceFetchHistory } from './forceFetchHistory'

export const createOutcome = (outcomeFields: OutcomeModel) =>
fetchOrFail(outcomeFetchingActions, async (dispatch, getApi) => {
fetchOrFail(outcomeFetchingActions, async (dispatch, getApi, getState) => {
await createOutcomeRequest(getApi())(outcomeFields)

await dispatch(fetchFirstTransactionDate() as any)

// TODO: refetch history
const historyCachedPerios = getHistoryCachedPeriods(getState())

await Promise.all(
historyCachedPerios.map(({ from, to, groupBy }) =>
dispatch(forceFetchHistory(from, to, groupBy) as any),
),
)
})
12 changes: 8 additions & 4 deletions front/src/domain/money/actions/fetchHistory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@ import { fetchOrFail } from '@front/domain/store/fetchingRedux/fetchOrFail'
import { GroupBy } from '@shared/enum/GroupBy'

import { fetchHistoryRequest } from '../api/fetchHistoryRequest'
import { createHistoryKey } from '../helpers/createHistoryKey'
import { actions as dataActions } from '../reducer/history'
import { actions as historyFetchingActions } from '../reducer/historyFetching'
import { getHistory } from '../selectors/getHistory'

const { addHistory } = dataActions

export const fetchHistory = (from: Date, to: Date, groupBy: GroupBy) =>
fetchOrFail(historyFetchingActions, async (dispatch, getApi) => {
const history = await fetchHistoryRequest(getApi())(from, to, groupBy)
fetchOrFail(historyFetchingActions, async (dispatch, getApi, getState) => {
const existHistory = getHistory(from, to, groupBy)(getState())

dispatch(addHistory(createHistoryKey(from, to, groupBy), history))
if (existHistory.isEmpty()) {
const history = await fetchHistoryRequest(getApi())(from, to, groupBy)

dispatch(addHistory({ from, to, groupBy }, history))
}
})
14 changes: 14 additions & 0 deletions front/src/domain/money/actions/forceFetchHistory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { fetchOrFail } from '@front/domain/store/fetchingRedux/fetchOrFail'
import { GroupBy } from '@shared/enum/GroupBy'

import { fetchHistoryRequest } from '../api/fetchHistoryRequest'
import { actions as dataActions } from '../reducer/history'

const { addHistory } = dataActions

export const forceFetchHistory = (from: Date, to: Date, groupBy: GroupBy) =>
fetchOrFail(undefined, async (dispatch, getApi) => {
const history = await fetchHistoryRequest(getApi())(from, to, groupBy)

dispatch(addHistory({ from, to, groupBy }, history))
})
49 changes: 40 additions & 9 deletions front/src/domain/money/reducer/history.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,64 @@
import { drop } from 'lodash'
import { ClearAction, createClearRedux } from 'redux-clear'

import { omitFirst } from '@front/helpers/omitFirst'
import { GroupBy } from '@shared/enum/GroupBy'
import { HistoryGroupModel } from '@shared/models/money/HistoryGroupModel'

interface State {
import { createHistoryKey } from '../helpers/createHistoryKey'

interface CachedPeriod {
groupBy: GroupBy
from: Date
to: Date
}

interface Data {
[key: string]: HistoryGroupModel[]
}

interface State {
data: Data
cachedPeriods: CachedPeriod[]
}

interface Actions {
addHistory: ClearAction<[string, HistoryGroupModel[]]>
addHistory: ClearAction<[CachedPeriod, HistoryGroupModel[]]>
}

const MAX_HISTORY_LENGTH = 10

const { actions, reducer } = createClearRedux<State, Actions>(
{
addHistory: state => (key, newHistory) => {
const itemsCount = Object.keys(state).length
addHistory: ({ data, cachedPeriods, ...state }) => (period, newHistory) => {
const { from, to, groupBy } = period
const key = createHistoryKey(from, to, groupBy)

const dataCount = Object.keys(data).length
const cachedPeriodsCount = cachedPeriods.length

const oldData = dataCount >= MAX_HISTORY_LENGTH ? omitFirst(data) : data

const oldState =
itemsCount > MAX_HISTORY_LENGTH ? omitFirst(state) : state
const oldPeriods =
cachedPeriodsCount >= MAX_HISTORY_LENGTH
? drop(cachedPeriods)
: cachedPeriods

// TODO: filter non-uniq cachedPeriods
return {
...oldState,
[key]: newHistory,
...state,
data: {
...oldData,
[key]: newHistory,
},
cachedPeriods: [...oldPeriods, period],
}
},
},
{},
{
data: {},
cachedPeriods: [],
},
)

export { State, actions, reducer }
2 changes: 1 addition & 1 deletion front/src/domain/money/selectors/getHistory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ import { createHistoryKey } from '../helpers/createHistoryKey'

export const getHistory = (from: Date, to: Date, groupBy: GroupBy) => (
state: State,
) => Option.of(state.money.history[createHistoryKey(from, to, groupBy)])
) => Option.of(state.money.history.data[createHistoryKey(from, to, groupBy)])
4 changes: 4 additions & 0 deletions front/src/domain/money/selectors/getHistoryCachedPeriods.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { State } from '@front/domain/store/State'

export const getHistoryCachedPeriods = (state: State) =>
state.money.history.cachedPeriods
18 changes: 18 additions & 0 deletions front/src/features/app/features/history/Groupment.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { GroupBy } from '@shared/enum/GroupBy'

interface Props {
groupBy: GroupBy
updateGroupBy: (newGroupBy: GroupBy) => void
}

export const Groupment = ({ groupBy, updateGroupBy }: Props) => (
<>
{Object.values(GroupBy).map(value =>
value === groupBy ? (
<span>{value}</span>
) : (
<button onClick={() => updateGroupBy(value)}>{value}</button>
),
)}
</>
)
11 changes: 8 additions & 3 deletions front/src/features/app/features/history/History.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ import { getHistory } from '@front/domain/money/selectors/getHistory'
import { Loader } from '@front/ui/loader'
import { GroupBy } from '@shared/enum/GroupBy'

import { Groupment } from './Groupment'
import { Period } from './Period'

export const History = () => {
const firstTransactionDate = useMappedState(getFirstTransactionDate)

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

const fetching = useMappedState(getFetchingStatus)

Expand All @@ -36,6 +39,8 @@ export const History = () => {
return (
<>
<h2>History</h2>
<Groupment groupBy={groupBy} updateGroupBy={setGroupBy} />
<Period start={from} updateStart={setFrom} end={to} updateEnd={setTo} />
<Loader status={fetching}>
{history
.map(realHistory =>
Expand Down
49 changes: 49 additions & 0 deletions front/src/features/app/features/history/Period.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { format } from 'date-fns'
import { ChangeEvent, useCallback } from 'react'

interface Props {
start: Date
updateStart: (newStart: Date) => void
end: Date
updateEnd: (newEnd: Date) => void
}

export const Period = ({ start, end, updateStart, updateEnd }: Props) => {
const createHandle = useCallback(
(update: (d: Date) => void) => ({
target,
}: ChangeEvent<HTMLInputElement>) => {
if (target.value) {
return update(new Date(target.value))
}
},
[],
)

const formatForInput = useCallback(
(date: Date) => format(date, 'YYYY-MM-DD'),
[],
)

return (
<>
<label>
<input
type="date"
value={formatForInput(start)}
onChange={createHandle(updateStart)}
/>
Start
</label>

<label>
<input
type="date"
value={formatForInput(end)}
onChange={createHandle(updateEnd)}
/>
End
</label>
</>
)
}
8 changes: 6 additions & 2 deletions front/src/helpers/omitFirst.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { head, omitBy } from 'lodash'
import { head, omit } from 'lodash'

export const omitFirst = (obj: object) => {
const firstKey = head(Object.keys(obj))

return omitBy(obj, key => key === firstKey)
if (!firstKey) {
return obj
}

return omit(obj, [firstKey])
}

0 comments on commit 29ea988

Please sign in to comment.