diff --git a/package.json b/package.json index f94c0a58c..af7cc81ad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bfx-report-ui", - "version": "2.42.1", + "version": "2.43.0", "description": "Report page to overview the user actions in Bitfinex and download related csv files", "repository": { "type": "git", diff --git a/public/locales/en/translations.json b/public/locales/en/translations.json index 5f2f58634..d7edfbfaa 100644 --- a/public/locales/en/translations.json +++ b/public/locales/en/translations.json @@ -556,6 +556,7 @@ }, "selector": { "select": "Select", + "strategy": "Strategy", "all": "All", "inactive": "Inactive", "balance-precision": { @@ -727,16 +728,37 @@ }, "taxreport": { "title": "Tax Report", - "sections": { - "startSnapshot": "Start Snapshot", - "endSnapshot": "End Snapshot", - "finalResult": "Final Result" - }, "startingPeriodBalances": "Starting Period Balances", "endingPeriodBalances": "Ending Period Balances", "startPositions": "Starting Positions Snapshot", "endPositions": "Ending Positions Snapshot", - "movements": "Movements" + "movements": "Movements", + "cols": { + "currency": "Currency", + "source": "Source", + "amount": "Amount", + "dateAcquired": "Date Acquired ", + "dateSold": "Date Sold", + "proceeds": "Proceeds", + "cost": "Cost", + "gainOrLoss": "Gain or Loss" + }, + "sources":{ + "airdrop_on_wallet": "Airdrop on wallet", + "margin_funding_payment": "Margin funding payment", + "affiliate_rebate": "Affiliate rebate", + "staking_payment": "Staking payment", + "exchange": "Exchange" + }, + "disclaimer": { + "title": "Disclaimer", + "message": "The tax reports generated by this app are for informational purposes only. We do not guarantee accuracy or completeness. Always consult a qualified tax advisor to ensure compliance with current tax laws and personalized advice. Your reliance on the generated reports is at your own risk." + }, + "generation": { + "success": "Tax Report generated", + "title": "Your tax report is being generated. This process can take a while.", + "note": "If you have a large history it's recommended to keep the window open in the background until it's completed." + } }, "theme": { "light": "Light", diff --git a/src/components/Auth/LoginOtp/LoginOtp.js b/src/components/Auth/LoginOtp/LoginOtp.js index 82ffc5657..ca8f9e453 100644 --- a/src/components/Auth/LoginOtp/LoginOtp.js +++ b/src/components/Auth/LoginOtp/LoginOtp.js @@ -3,7 +3,7 @@ import { useSelector } from 'react-redux' import PropTypes from 'prop-types' import { useTranslation } from 'react-i18next' import { Button, Intent } from '@blueprintjs/core' -import { isEmpty } from '@bitfinex/lib-js-util-base' +import { isEmpty, isEqual } from '@bitfinex/lib-js-util-base' import useKeyDown from 'hooks/useKeyDown' import { getIsAuthBtnDisabled } from 'state/auth/selectors' @@ -26,7 +26,7 @@ export const LoginOtp = ({ }, ['Enter']) useEffect(() => { - if (otp?.length === 6) { + if (isEqual(otp?.length, 6) && !isAuthBtnDisabled) { handleOneTimePassword() } }, [otp]) @@ -55,7 +55,7 @@ export const LoginOtp = ({ intent={Intent.SUCCESS} className='bitfinex-auth-check' onClick={handleOneTimePassword} - disabled={isEmpty(otp || isAuthBtnDisabled)} + disabled={isEmpty(otp) || isAuthBtnDisabled} > {t('auth.2FA.auth')} diff --git a/src/components/TaxReport/Result/Balances.columns.js b/src/components/TaxReport/Result/Balances.columns.js deleted file mode 100644 index 13fd0fdb2..000000000 --- a/src/components/TaxReport/Result/Balances.columns.js +++ /dev/null @@ -1,73 +0,0 @@ -import React from 'react' -import { Cell } from '@blueprintjs/table' - -import { fixedFloat, formatAmount } from 'ui/utils' -import { getCellLoader, getCellNoData, getTooltipContent } from 'utils/columns' - -export default function getColumns(props) { - const { - t, - isNoData, - isLoading, - totalResult, - positionsTotalPlUsd, - walletsTotalBalanceUsd, - } = props - - return [ - { - id: 'walletsTotal', - name: 'column.walletsTotal', - width: 240, - renderer: () => { - if (isLoading) return getCellLoader(14, 72) - if (isNoData) return getCellNoData(t('column.noResults')) - return ( - - {formatAmount(walletsTotalBalanceUsd)} - - ) - }, - copyText: () => fixedFloat(walletsTotalBalanceUsd), - }, - { - id: 'positionsTotal', - name: 'column.positionsTotal', - width: 210, - renderer: () => { - if (isLoading) return getCellLoader(14, 72) - if (isNoData) return getCellNoData() - return ( - - {formatAmount(positionsTotalPlUsd)} - - ) - }, - copyText: () => fixedFloat(positionsTotalPlUsd), - }, - { - id: 'totalResult', - name: 'column.totalResult', - width: 160, - renderer: () => { - if (isLoading) return getCellLoader(14, 72) - if (isNoData) return getCellNoData() - return ( - - {formatAmount(totalResult)} - - ) - }, - copyText: () => fixedFloat(totalResult), - }, - ] -} diff --git a/src/components/TaxReport/Result/Result.container.js b/src/components/TaxReport/Result/Result.container.js deleted file mode 100644 index 32286908a..000000000 --- a/src/components/TaxReport/Result/Result.container.js +++ /dev/null @@ -1,35 +0,0 @@ -import { connect } from 'react-redux' -import { compose } from 'redux' -import { withRouter } from 'react-router-dom' -import { withTranslation } from 'react-i18next' - -import { fetchTaxReport, refresh } from 'state/taxReport/actions' -import { - getData, - getDataReceived, - getPageLoading, -} from 'state/taxReport/selectors' -import { getIsSyncRequired } from 'state/sync/selectors' -import { getFullTime, getTimeOffset } from 'state/base/selectors' - -import Result from './Result' - -const mapStateToProps = state => ({ - data: getData(state), - getFullTime: getFullTime(state), - timeOffset: getTimeOffset(state), - pageLoading: getPageLoading(state), - dataReceived: getDataReceived(state), - isSyncRequired: getIsSyncRequired(state), -}) - -const mapDispatchToProps = { - refresh, - fetchData: fetchTaxReport, -} - -export default compose( - connect(mapStateToProps, mapDispatchToProps), - withTranslation('translations'), - withRouter, -)(Result) diff --git a/src/components/TaxReport/Result/Result.js b/src/components/TaxReport/Result/Result.js deleted file mode 100644 index f29321ed3..000000000 --- a/src/components/TaxReport/Result/Result.js +++ /dev/null @@ -1,286 +0,0 @@ -import React, { PureComponent } from 'react' -import PropTypes from 'prop-types' -import _isNumber from 'lodash/isNumber' -import { isEmpty } from '@bitfinex/lib-js-util-base' - -import DataTable from 'ui/DataTable' -import { fixedFloat } from 'ui/utils' -import queryConstants from 'state/query/constants' -import { checkFetch, checkInit } from 'state/utils' -import { getFrameworkPositionsColumns } from 'utils/columns' -import getMovementsColumns from 'components/Movements/Movements.columns' - -import getBalancesColumns from './Balances.columns' -import TAX_REPORT_SECTIONS from '../TaxReport.sections' - -const TYPE = queryConstants.MENU_TAX_REPORT - -class Result extends PureComponent { - componentDidMount() { - checkInit(this.props, TYPE) - } - - componentDidUpdate(prevProps) { - checkFetch(prevProps, this.props, TYPE) - } - - getPositionsSnapshot = ({ positions, title, isLoading }) => { - const { - t, - timeOffset, - getFullTime, - } = this.props - - const positionsColumns = getFrameworkPositionsColumns({ - t, - isLoading, - timeOffset, - getFullTime, - filteredData: positions, - isNoData: isEmpty(positions), - }) - - return ( - <> -
- {title} -
- - - ) - } - - getBalances = ({ balances, title, isLoading }) => { - const { t } = this.props - const isNoData = this.isBalancesEmpty(balances) - const { - totalResult, - positionsTotalPlUsd, - walletsTotalBalanceUsd, - } = balances - - const balancesColumns = getBalancesColumns({ - t, - isNoData, - isLoading, - totalResult, - positionsTotalPlUsd, - walletsTotalBalanceUsd, - }) - - return ( - <> -
- {title} -
- - - ) - } - - getMovements = (isNoData, isLoading) => { - const { - t, - data, - timeOffset, - getFullTime, - } = this.props - const { - movements, - } = data.finalState - - const movementsColumns = getMovementsColumns({ - t, - isNoData, - isLoading, - timeOffset, - getFullTime, - filteredData: movements, - }) - - return ( - <> -
- {t('taxreport.movements')} -
- - - ) - } - - isBalancesEmpty = (balances) => { - const { - totalResult, - positionsTotalPlUsd, - walletsTotalBalanceUsd, - } = balances - - return !_isNumber(walletsTotalBalanceUsd) - && !_isNumber(positionsTotalPlUsd) - && !totalResult - } - - refresh = () => { - const { refresh } = this.props - refresh(TAX_REPORT_SECTIONS.RESULT) - } - - render() { - const { - t, - data, - pageLoading, - dataReceived, - } = this.props - const { - startingPositionsSnapshot, - endingPositionsSnapshot, - finalState: { - startingPeriodBalances, - endingPeriodBalances, - movements, - movementsTotalAmount, - totalResult, - }, - } = data - const isLoading = !dataReceived && pageLoading - const isNoData = !startingPositionsSnapshot.length - && !endingPositionsSnapshot.length - && this.isBalancesEmpty(startingPeriodBalances) - && this.isBalancesEmpty(endingPeriodBalances) - && !movements.length - && !_isNumber(movementsTotalAmount) - && !totalResult // can be 0 even if data is absent - - return ( - <> -
- {_isNumber(totalResult) && ( -
-
- {t('column.totalResult')} -
- {fixedFloat(totalResult)} -
- )} - {_isNumber(movementsTotalAmount) && ( -
-
- {t('column.movementsTotal')} -
- {fixedFloat(movementsTotalAmount)} -
- )} -
- {this.getMovements(isNoData, isLoading)} - {this.getPositionsSnapshot({ - isLoading, - positions: startingPositionsSnapshot, - title: t('taxreport.startPositions'), - })} -
- {this.getPositionsSnapshot({ - isLoading, - positions: endingPositionsSnapshot, - title: t('taxreport.endPositions'), - })} -
- {this.getBalances({ - isLoading, - balances: startingPeriodBalances, - title: t('taxreport.startingPeriodBalances'), - })} -
- {this.getBalances({ - isLoading, - balances: endingPeriodBalances, - title: t('taxreport.endingPeriodBalances'), - })} - - ) - } -} - -Result.propTypes = { - data: PropTypes.shape({ - startingPositionsSnapshot: PropTypes.arrayOf( - PropTypes.shape({ - amount: PropTypes.number, - basePrice: PropTypes.number, - liquidationPrice: PropTypes.number, - marginFunding: PropTypes.number, - marginFundingType: PropTypes.number, - mtsUpdate: PropTypes.number, - pair: PropTypes.string.isRequired, - pl: PropTypes.number, - plPerc: PropTypes.number, - }), - ).isRequired, - endingPositionsSnapshot: PropTypes.arrayOf( - PropTypes.shape({ - amount: PropTypes.number, - basePrice: PropTypes.number, - liquidationPrice: PropTypes.number, - marginFunding: PropTypes.number, - marginFundingType: PropTypes.number, - mtsUpdate: PropTypes.number, - pair: PropTypes.string.isRequired, - pl: PropTypes.number, - plPerc: PropTypes.number, - }), - ).isRequired, - finalState: PropTypes.shape({ - startingPeriodBalances: PropTypes.shape({ - walletsTotalBalanceUsd: PropTypes.number, - positionsTotalPlUsd: PropTypes.number, - totalResult: PropTypes.number, - }), - movements: PropTypes.arrayOf(PropTypes.shape({ - amount: PropTypes.number, - amountUsd: PropTypes.number, - currency: PropTypes.string, - currencyName: PropTypes.string, - destinationAddress: PropTypes.string, - fees: PropTypes.number, - id: PropTypes.number, - mtsStarted: PropTypes.number, - mtsUpdated: PropTypes.number, - note: PropTypes.string, - status: PropTypes.string, - subUserId: PropTypes.number, - transactionId: PropTypes.string, - })).isRequired, - movementsTotalAmount: PropTypes.number, - endingPeriodBalances: PropTypes.shape({ - walletsTotalBalanceUsd: PropTypes.number, - positionsTotalPlUsd: PropTypes.number, - totalResult: PropTypes.number, - }), - totalResult: PropTypes.number, - }).isRequired, - }).isRequired, - pageLoading: PropTypes.bool.isRequired, - dataReceived: PropTypes.bool.isRequired, - getFullTime: PropTypes.func.isRequired, - timeOffset: PropTypes.string.isRequired, - refresh: PropTypes.func.isRequired, - t: PropTypes.func.isRequired, -} - -export default Result diff --git a/src/components/TaxReport/Result/index.js b/src/components/TaxReport/Result/index.js deleted file mode 100644 index 79367ad8a..000000000 --- a/src/components/TaxReport/Result/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './Result.container' diff --git a/src/components/TaxReport/Snapshot/Snapshot.container.js b/src/components/TaxReport/Snapshot/Snapshot.container.js deleted file mode 100644 index 628affcea..000000000 --- a/src/components/TaxReport/Snapshot/Snapshot.container.js +++ /dev/null @@ -1,38 +0,0 @@ -import { connect } from 'react-redux' -import { compose } from 'redux' -import { withRouter } from 'react-router-dom' -import { withTranslation } from 'react-i18next' - -import { fetchTaxReportSnapshot, refresh } from 'state/taxReport/actions' -import { - getSnapshot, - getSnapshotPageLoading, - getSnapshotDataReceived, -} from 'state/taxReport/selectors' -import { getIsSyncRequired } from 'state/sync/selectors' - -import Snapshot from './Snapshot' - -const mapStateToProps = (state, { match }) => { - const { section: snapshotSection } = match.params - return { - data: getSnapshot(state, snapshotSection), - pageLoading: getSnapshotPageLoading(state, snapshotSection), - dataReceived: getSnapshotDataReceived(state, snapshotSection), - isSyncRequired: getIsSyncRequired(state), - } -} - -const mapDispatchToProps = (dispatch, { match }) => { - const { section: snapshotSection } = match.params - return { - refresh: () => dispatch(refresh({ section: snapshotSection })), - fetchData: () => dispatch(fetchTaxReportSnapshot(snapshotSection)), - } -} - -export default compose( - withRouter, - withTranslation('translations'), - connect(mapStateToProps, mapDispatchToProps), -)(Snapshot) diff --git a/src/components/TaxReport/Snapshot/Snapshot.js b/src/components/TaxReport/Snapshot/Snapshot.js deleted file mode 100644 index fb5376333..000000000 --- a/src/components/TaxReport/Snapshot/Snapshot.js +++ /dev/null @@ -1,132 +0,0 @@ -import React, { PureComponent } from 'react' -import { Button, ButtonGroup, Intent } from '@blueprintjs/core' - -import WalletsSnapshot from 'components/Snapshots/WalletsSnapshot' -import TickersSnapshot from 'components/Snapshots/TickersSnapshot' -import PositionsSnapshot from 'components/Snapshots/PositionsSnapshot' -import queryConstants from 'state/query/constants' -import { checkFetch, checkInit } from 'state/utils' - -import { propTypes, defaultProps } from './Snapshots.props' - -const { - MENU_TAX_REPORT, - MENU_POSITIONS, - MENU_TICKERS, - MENU_WALLETS, -} = queryConstants - -class Snapshot extends PureComponent { - componentDidMount() { - checkInit(this.props, MENU_TAX_REPORT) - } - - componentDidUpdate(prevProps) { - checkFetch(prevProps, this.props, MENU_TAX_REPORT) - } - - getSectionURL = (subsection) => { - const { match } = this.props - const { section } = match.params - - switch (subsection) { - case MENU_POSITIONS: - return `/tax_report/${section}/positions` - case MENU_TICKERS: - return `/tax_report/${section}/tickers` - case MENU_WALLETS: - return `/tax_report/${section}/wallets` - default: - return '' - } - } - - switchSection = (section) => { - const { history } = this.props - const path = this.getSectionURL(section) - - history.push(`${path}${window.location.search}`) - } - - render() { - const { - t, - data, - match, - pageLoading, - dataReceived, - } = this.props - const { - walletsEntries, - positionsEntries, - positionsTotalPlUsd, - walletsTickersEntries, - walletsTotalBalanceUsd, - positionsTickersEntries, - } = data - const { subsection } = match.params - const isLoading = !dataReceived && pageLoading - const isNoData = (subsection === MENU_POSITIONS && !positionsEntries.length) - || (subsection === MENU_TICKERS && !positionsTickersEntries.length && !walletsTickersEntries) - || (subsection === MENU_WALLETS && !walletsEntries.length) - - let showContent - if (subsection === MENU_WALLETS) { - showContent = ( - - ) - } else if (subsection === MENU_POSITIONS) { - showContent = ( - - ) - } else { - showContent = ( - - ) - } - - return ( -
- - - - - - {showContent} -
- ) - } -} - -Snapshot.propTypes = propTypes -Snapshot.defaultProps = defaultProps - -export default Snapshot diff --git a/src/components/TaxReport/Snapshot/Snapshots.props.js b/src/components/TaxReport/Snapshot/Snapshots.props.js deleted file mode 100644 index 3e3d05d5f..000000000 --- a/src/components/TaxReport/Snapshot/Snapshots.props.js +++ /dev/null @@ -1,19 +0,0 @@ -import PropTypes from 'prop-types' - -export const propTypes = { - data: PropTypes.shape({ - positionsTotalPlUsd: PropTypes.number, - positionsEntries: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types - positionsTickersEntries: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types - walletsTotalBalanceUsd: PropTypes.number, - walletsTickersEntries: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types - walletsEntries: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types - }).isRequired, - fetchData: PropTypes.func.isRequired, - dataReceived: PropTypes.bool.isRequired, - pageLoading: PropTypes.bool.isRequired, - refresh: PropTypes.func.isRequired, - t: PropTypes.func.isRequired, -} - -export const defaultProps = {} diff --git a/src/components/TaxReport/Snapshot/index.js b/src/components/TaxReport/Snapshot/index.js deleted file mode 100644 index bde8afd32..000000000 --- a/src/components/TaxReport/Snapshot/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './Snapshot.container' diff --git a/src/components/TaxReport/TaxReport.columns.js b/src/components/TaxReport/TaxReport.columns.js new file mode 100644 index 000000000..cdae229b5 --- /dev/null +++ b/src/components/TaxReport/TaxReport.columns.js @@ -0,0 +1,112 @@ +import { mapSymbol } from 'state/symbols/utils' +import { formatAmount, fixedFloat } from 'ui/utils' +import { + getCell, + getCellState, + getColumnWidth, + formatSourceType, +} from 'utils/columns' + +export const getColumns = ({ + t, + entries, + isNoData, + isLoading, + getFullTime, + columnsWidth, +}) => [ + { + id: 'asset', + width: getColumnWidth('asset', columnsWidth), + name: 'taxreport.cols.currency', + className: 'align-left', + renderer: (rowIndex) => { + if (isLoading || isNoData) return getCellState(isLoading, isNoData) + const { asset } = entries[rowIndex] + return getCell(mapSymbol(asset), t) + }, + copyText: rowIndex => mapSymbol(entries[rowIndex].asset), + }, + { + id: 'type', + width: getColumnWidth('type', columnsWidth), + name: 'taxreport.cols.source', + className: 'align-left', + renderer: (rowIndex) => { + if (isLoading || isNoData) return getCellState(isLoading, isNoData) + const { type } = entries[rowIndex] + return getCell(formatSourceType(type, t), t) + }, + copyText: rowIndex => formatSourceType(entries[rowIndex].type, t), + }, + { + id: 'amount', + width: getColumnWidth('amount', columnsWidth), + name: 'taxreport.cols.amount', + renderer: (rowIndex) => { + if (isLoading || isNoData) return getCellState(isLoading, isNoData) + const { amount } = entries[rowIndex] + return getCell(formatAmount(amount), t, fixedFloat(amount)) + }, + isNumericValue: true, + copyText: rowIndex => fixedFloat(entries[rowIndex].amount), + }, + { + id: 'mtsAcquired', + width: getColumnWidth('mtsAcquired', columnsWidth), + name: 'taxreport.cols.dateAcquired', + renderer: (rowIndex) => { + if (isLoading || isNoData) return getCellState(isLoading, isNoData) + const timestamp = getFullTime(entries[rowIndex].mtsAcquired) + return getCell(timestamp, t) + }, + copyText: rowIndex => getFullTime(entries[rowIndex].mtsAcquired), + }, + { + id: 'mtsSold', + width: getColumnWidth('mtsSold', columnsWidth), + name: 'taxreport.cols.dateSold', + renderer: (rowIndex) => { + if (isLoading || isNoData) return getCellState(isLoading, isNoData) + const timestamp = getFullTime(entries[rowIndex].mtsSold) + return getCell(timestamp, t) + }, + copyText: rowIndex => getFullTime(entries[rowIndex].mtsSold), + }, + { + id: 'proceeds', + width: getColumnWidth('proceeds', columnsWidth), + name: 'taxreport.cols.proceeds', + renderer: (rowIndex) => { + if (isLoading || isNoData) return getCellState(isLoading, isNoData) + const { proceeds } = entries[rowIndex] + return getCell(formatAmount(proceeds), t, fixedFloat(proceeds)) + }, + isNumericValue: true, + copyText: rowIndex => fixedFloat(entries[rowIndex].proceeds), + }, + { + id: 'cost', + width: getColumnWidth('cost', columnsWidth), + name: 'taxreport.cols.cost', + renderer: (rowIndex) => { + if (isLoading || isNoData) return getCellState(isLoading, isNoData) + const { cost } = entries[rowIndex] + return getCell(formatAmount(cost), t, fixedFloat(cost)) + }, + isNumericValue: true, + copyText: rowIndex => fixedFloat(entries[rowIndex].cost), + }, + { + id: 'gainOrLoss', + width: getColumnWidth('gainOrLoss', columnsWidth), + name: 'taxreport.cols.gainOrLoss', + renderer: (rowIndex) => { + if (isLoading || isNoData) return getCellState(isLoading, isNoData) + const { gainOrLoss } = entries[rowIndex] + return getCell(formatAmount(gainOrLoss), t, fixedFloat(gainOrLoss)) + }, + isNumericValue: true, + copyText: rowIndex => fixedFloat(entries[rowIndex].gainOrLoss), + }, +] diff --git a/src/components/TaxReport/TaxReport.container.js b/src/components/TaxReport/TaxReport.container.js deleted file mode 100644 index a6d7ec35e..000000000 --- a/src/components/TaxReport/TaxReport.container.js +++ /dev/null @@ -1,18 +0,0 @@ -import { connect } from 'react-redux' -import { compose } from 'redux' -import { withRouter } from 'react-router-dom' -import { withTranslation } from 'react-i18next' - -import { refresh } from 'state/taxReport/actions' - -import TaxReport from './TaxReport' - -const mapDispatchToProps = { - refresh, -} - -export default compose( - connect(null, mapDispatchToProps), - withTranslation('translations'), - withRouter, -)(TaxReport) diff --git a/src/components/TaxReport/TaxReport.disclaimer.js b/src/components/TaxReport/TaxReport.disclaimer.js new file mode 100644 index 000000000..499fdbcaa --- /dev/null +++ b/src/components/TaxReport/TaxReport.disclaimer.js @@ -0,0 +1,28 @@ +import React from 'react' +import { useDispatch } from 'react-redux' +import { useTranslation } from 'react-i18next' + +import Icon from 'icons' +import { setShowDisclaimer } from 'state/taxReport/actions' + +const Disclaimer = () => { + const { t } = useTranslation() + const dispatch = useDispatch() + const onClose = () => dispatch(setShowDisclaimer(false)) + + return ( +
+

+ {t('taxreport.disclaimer.title')} +

+
+ {t('taxreport.disclaimer.message')} +
+ +
+
+
+ ) +} + +export default Disclaimer diff --git a/src/components/TaxReport/TaxReport.js b/src/components/TaxReport/TaxReport.js index a8fb0550b..4387e56eb 100644 --- a/src/components/TaxReport/TaxReport.js +++ b/src/components/TaxReport/TaxReport.js @@ -1,95 +1,132 @@ -import React, { PureComponent } from 'react' +import React, { useMemo, useEffect, useCallback } from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { useTranslation } from 'react-i18next' import { Card, Elevation } from '@blueprintjs/core' +import { isEmpty } from '@bitfinex/lib-js-util-base' +import DataTable from 'ui/DataTable' import { SectionHeader, + SectionHeaderRow, + SectionHeaderItem, SectionHeaderTitle, + SectionHeaderItemLabel, } from 'ui/SectionHeader' import TimeRange from 'ui/TimeRange' -import NavSwitcher from 'ui/NavSwitcher' - -import Result from './Result' -import Snapshot from './Snapshot' -import { propTypes } from './TaxReport.props' -import TAX_REPORT_SECTIONS from './TaxReport.sections' - -const { - START_SNAPSHOT, - END_SNAPSHOT, - RESULT, -} = TAX_REPORT_SECTIONS +import RefreshButton from 'ui/RefreshButton' +import TaxStrategySelector from 'ui/TaxStrategySelector' +import { fetchTaxReportTransactions } from 'state/taxReport/actions' +import { + getTransactionsData, + getTransactionsPageLoading, + getTransactionsDataReceived, + getTransactionsShowDisclaimer, +} from 'state/taxReport/selectors' +import { getIsSyncRequired } from 'state/sync/selectors' +import { getColumnsWidth } from 'state/columns/selectors' +import { getFullTime as getFullTimeSelector } from 'state/base/selectors' -const SECTIONS_URL = { - START_SNAPSHOT: '/tax_report/start_snapshot', - END_SNAPSHOT: '/tax_report/end_snapshot', - RESULT: '/tax_report/result', -} +import queryConstants from 'state/query/constants' -class TaxReport extends PureComponent { - switchSection = (section) => { - const { history } = this.props +import Loader from './TaxReport.loader' +import Disclaimer from './TaxReport.disclaimer' +import { getColumns } from './TaxReport.columns' - const path = this.getSectionURL(section) - history.push(`${path}${window.location.search}`) - } +const TYPE = queryConstants.MENU_TAX_REPORT - getSection = (section) => { - switch (section) { - case START_SNAPSHOT: - return - case END_SNAPSHOT: - return - case RESULT: - default: - return - } - } +const TaxReport = () => { + const { t } = useTranslation() + const dispatch = useDispatch() + const entries = useSelector(getTransactionsData) + const getFullTime = useSelector(getFullTimeSelector) + const isSyncRequired = useSelector(getIsSyncRequired) + const pageLoading = useSelector(getTransactionsPageLoading) + const dataReceived = useSelector(getTransactionsDataReceived) + const showDisclaimer = useSelector(getTransactionsShowDisclaimer) + const columnsWidth = useSelector((state) => getColumnsWidth(state, TYPE)) + const isNoData = isEmpty(entries) + const isLoading = !dataReceived && pageLoading + const shouldFetchTaxReport = !isSyncRequired && !dataReceived && !isLoading - getSectionURL = (section) => { - switch (section) { - case START_SNAPSHOT: - return `${SECTIONS_URL.START_SNAPSHOT}/positions` - case END_SNAPSHOT: - return `${SECTIONS_URL.END_SNAPSHOT}/positions` - case RESULT: - return SECTIONS_URL.RESULT - default: - return '' - } - } + useEffect(() => { + if (shouldFetchTaxReport) dispatch(fetchTaxReportTransactions()) + }, [shouldFetchTaxReport]) - render() { - const { match, t } = this.props - const { section = RESULT } = match.params + const onRefresh = useCallback( + () => dispatch(fetchTaxReportTransactions()), + [dispatch], + ) - return ( - - - - {t('taxreport.title')} - - - + const columns = useMemo( + () => getColumns({ + t, entries, isNoData, isLoading, getFullTime, columnsWidth, + }), + [t, entries, isNoData, isLoading, getFullTime, columnsWidth], + ) - + } else if (isNoData) { + showContent = ( +
+ - - {this.getSection(section)} - +
+ ) + } else { + showContent = ( + <> + + ) } -} -TaxReport.propTypes = propTypes + return ( + + + + {t('taxreport.title')} + + {showDisclaimer && ( + + + + )} + + + + {t('selector.filter.date')} + + + + + + {t('selector.strategy')} + + + + + + + {showContent} + + ) +} export default TaxReport diff --git a/src/components/TaxReport/TaxReport.loader.js b/src/components/TaxReport/TaxReport.loader.js new file mode 100644 index 000000000..9abb58f76 --- /dev/null +++ b/src/components/TaxReport/TaxReport.loader.js @@ -0,0 +1,33 @@ +import React from 'react' +import { useSelector } from 'react-redux' +import { useTranslation } from 'react-i18next' +import { Spinner } from '@blueprintjs/core' +import { isNil } from '@bitfinex/lib-js-util-base' + +import { getTransactionsGenerationProgress } from 'state/taxReport/selectors' + +export const Loader = () => { + const { t } = useTranslation() + const progress = useSelector(getTransactionsGenerationProgress) + const spinnerContent = isNil(progress) ? '' : `${progress}%` + + return ( +
+
+
+ {spinnerContent} +
+ +
+
+

{t('taxreport.generation.title')}

+

{t('taxreport.generation.note')}

+
+
+ ) +} + +export default Loader diff --git a/src/components/TaxReport/TaxReport.props.js b/src/components/TaxReport/TaxReport.props.js deleted file mode 100644 index 68b698b76..000000000 --- a/src/components/TaxReport/TaxReport.props.js +++ /dev/null @@ -1,7 +0,0 @@ -/* eslint-disable import/prefer-default-export */ -import PropTypes from 'prop-types' - -export const propTypes = { - refresh: PropTypes.func.isRequired, - t: PropTypes.func.isRequired, -} diff --git a/src/components/TaxReport/TaxReport.sections.js b/src/components/TaxReport/TaxReport.sections.js deleted file mode 100644 index 4f7ece981..000000000 --- a/src/components/TaxReport/TaxReport.sections.js +++ /dev/null @@ -1,7 +0,0 @@ -const TAX_REPORT_SECTIONS = { - START_SNAPSHOT: 'start_snapshot', - END_SNAPSHOT: 'end_snapshot', - RESULT: 'result', -} - -export default TAX_REPORT_SECTIONS diff --git a/src/components/TaxReport/_TaxReport.scss b/src/components/TaxReport/_TaxReport.scss index 8a8ce8538..101108206 100644 --- a/src/components/TaxReport/_TaxReport.scss +++ b/src/components/TaxReport/_TaxReport.scss @@ -1,20 +1,70 @@ .tax-report { - .snapshot { - height: 100%; + .section-header { + margin-bottom: 10px; - .bp3-button-group { - transform: translateY(-20px); - margin-bottom: 10px; + &-row { + margin-bottom: 0; + margin-top: 10px; } } - .bp3-table-container { - &:last-child { - margin-bottom: 40px; - } + .loading { + &.bp3-spinner { + margin: auto; + display: block; + } + + &-container { + height: 60%; + display: flex; + position: relative; + align-items: center; + flex-direction: column; + justify-content: center; + + .spinner-wrapper { + display: flex; + position: relative; + margin-bottom: 20px; + } + } + + &-progress { + top: 50%; + left: 50%; + position: absolute; + line-height: initial; + transform: translate(-50%, -50%); + } + + &-note { + min-width: 340px; + text-align: center; + } + } +} + +.disclaimer { + position: relative; + padding: 14px; + font-size: 14px; + border-radius: 4px; + padding-bottom: 18px; + background-color: var(--bgColor); + + &-header { + font-weight: 600; + margin-bottom: 6px; + } + + &-body { + font-weight: 400; } - .movements-table { - margin-bottom: 40px; + &-icon { + top: 10px; + right: 10px; + cursor: pointer; + position: absolute; } } diff --git a/src/components/TaxReport/index.js b/src/components/TaxReport/index.js index 2486a2101..8234bd0cb 100644 --- a/src/components/TaxReport/index.js +++ b/src/components/TaxReport/index.js @@ -1 +1 @@ -export { default } from './TaxReport.container' +export { default } from './TaxReport' diff --git a/src/state/query/saga.js b/src/state/query/saga.js index a4d6f34f2..e6f54c618 100644 --- a/src/state/query/saga.js +++ b/src/state/query/saga.js @@ -56,6 +56,7 @@ import { } from 'state/base/selectors' import { getEmail } from 'state/auth/selectors' import { getTimeFrame } from 'state/timeRange/selectors' +import { getTransactionsStrategy } from 'state/taxReport/selectors' import config from 'config' import actions from './actions' @@ -225,6 +226,7 @@ function* getOptions({ target }) { const isUnrealizedProfitExcluded = showFrameworkMode ? yield select(getIsUnrealizedProfitExcluded) : '' const isVsAccountBalanceSelected = showFrameworkMode ? yield select(getIsVsAccountBalanceSelected) : '' const isPdfExportRequired = showFrameworkMode ? yield select(getIsPdfExportRequired) : false + const taxReportStrategy = showFrameworkMode ? yield select(getTransactionsStrategy) : '' switch (target) { case MENU_ACCOUNT_BALANCE: @@ -264,6 +266,7 @@ function* getOptions({ target }) { break case MENU_TAX_REPORT: options.isPDFRequired = isPdfExportRequired + options.strategy = taxReportStrategy break case MENU_LOGINS: case MENU_CHANGE_LOGS: @@ -363,7 +366,7 @@ function* getOptions({ target }) { options.method = 'getFullSnapshotReportFile' break case MENU_TAX_REPORT: - options.method = 'getFullTaxReportFile' + options.method = 'getTransactionTaxReportFile' break case MENU_TRADED_VOLUME: options.method = 'getTradedVolumeFile' @@ -398,18 +401,6 @@ function* exportReport({ payload: targets }) { for (const target of targets) { const options = yield call(getOptions, { target }) multiExport.push(options) - - // add 2 additional snapshot reports - if (target === MENU_TAX_REPORT) { - multiExport.push({ - ...options, - isStartSnapshot: true, - }) - multiExport.push({ - ...options, - isEndSnapshot: true, - }) - } } const locale = yield select(getLocale) diff --git a/src/state/sync/saga.js b/src/state/sync/saga.js index 83a4ca108..e2e024ca6 100644 --- a/src/state/sync/saga.js +++ b/src/state/sync/saga.js @@ -211,9 +211,11 @@ function* progressUpdate({ payload }) { yield put(actions.setIsSyncing(false)) yield put(actions.showInitSyncPopup(false)) } else { + const isSyncing = yield select(getIsSyncing) const syncProgress = Number.isInteger(progress) ? progress : 0 + if (isSyncInProgress && !isSyncing) yield put(actions.setIsSyncing(true)) yield put(actions.setSyncProgress(syncProgress)) yield put(actions.setEstimatedTime(estimatedTimeValues)) } diff --git a/src/state/taxReport/actions.js b/src/state/taxReport/actions.js index bd69d0560..bebd6a62a 100644 --- a/src/state/taxReport/actions.js +++ b/src/state/taxReport/actions.js @@ -1,74 +1,51 @@ import types from './constants' -/** - * Create an action to fetch Tax Report data. - */ -export function fetchTaxReport() { +export function fetchFail(payload) { return { - type: types.FETCH_TAX_REPORT, + type: types.FETCH_FAIL, + payload, } } -/** - * Create an action to fetch Snapshot data. - * @param {string} payload section to fetch - */ -export function fetchTaxReportSnapshot(payload) { +export function fetchTaxReportTransactions() { return { - type: types.FETCH_SNAPSHOT, - payload, + type: types.FETCH_TRANSACTIONS, } } -/** - * Create an action to note fetch fail. - * @param {Object} payload fail message - */ -export function fetchFail(payload) { +export function updateTaxReportTransactions(payload) { return { - type: types.FETCH_FAIL, + type: types.UPDATE_TRANSACTIONS, payload, } } -/** - * Create an action to refresh Tax Report. - * * @param {object} payload object contains options - */ -export function refresh(payload) { +export function setTransactionsStrategy(payload) { return { - type: types.REFRESH, + type: types.SET_TRANSACTIONS_STRATEGY, payload, } } -/** - * Create an action to update Tax Report. - * @param {Object[]} payload data set - */ -export function updateTaxReport(payload) { +export function setShowDisclaimer(payload) { return { - type: types.UPDATE_TAX_REPORT, + type: types.SET_SHOW_DISCLAIMER, payload, } } -/** - * Create an action to update Tax Report Snapshot. - * @param {Object} payload data set and section - */ -export function updateTaxReportSnapshot(payload) { +export function setGenerationProgress(payload) { return { - type: types.UPDATE_TAX_REPORT_SNAPSHOT, + type: types.SET_GENERATION_PROGRESS, payload, } } export default { fetchFail, - fetchTaxReport, - fetchTaxReportSnapshot, - refresh, - updateTaxReport, - updateTaxReportSnapshot, + setShowDisclaimer, + setGenerationProgress, + setTransactionsStrategy, + fetchTaxReportTransactions, + updateTaxReportTransactions, } diff --git a/src/state/taxReport/constants.js b/src/state/taxReport/constants.js index 6d269ee82..0cf6ac55f 100644 --- a/src/state/taxReport/constants.js +++ b/src/state/taxReport/constants.js @@ -1,9 +1,13 @@ export default { FETCH_FAIL: 'BITFINEX/TAX_REPORT/FETCH/FAIL', - FETCH_TAX_REPORT: 'BITFINEX/TAX_REPORT/FETCH', - FETCH_SNAPSHOT: 'BITFINEX/TAX_REPORT/SNAPSHOT/FETCH', - FETCH_SNAPSHOT_FAIL: 'BITFINEX/TAX_REPORT/SNAPSHOT/FETCH/FAIL', - REFRESH: 'BITFINEX/TAX_REPORT/REFRESH', - UPDATE_TAX_REPORT: 'BITFINEX/TAX_REPORT/UPDATE', - UPDATE_TAX_REPORT_SNAPSHOT: 'BITFINEX/TAX_REPORT/SNAPSHOT/UPDATE', + FETCH_TRANSACTIONS: 'BITFINEX/TAX_REPORT_TRANSACTIONS/FETCH', + UPDATE_TRANSACTIONS: 'BITFINEX/TAX_REPORT_TRANSACTIONS/UPDATE', + SET_TRANSACTIONS_STRATEGY: 'BITFINEX/TAX_REPORT_TRANSACTIONS/STRATEGY/SET', + SET_SHOW_DISCLAIMER: 'BITFINEX/TAX_REPORT_TRANSACTIONS/SHOW_DISCLAIMER/SET', + SET_GENERATION_PROGRESS: 'BITFINEX/TAX_REPORT_TRANSACTIONS/GENERATION_PROGRESS/SET', + + STRATEGY_LIFO: 'LIFO', + STRATEGY_FIFO: 'FIFO', + WS_TAX_TRANSACTION_REPORT_GENERATION_PROGRESS: 'ws_emitTrxTaxReportGenerationProgressToOne', + WS_TAX_TRANSACTION_REPORT_GENERATION_COMPLETED: 'ws_emitTrxTaxReportGenerationInBackgroundToOne', } diff --git a/src/state/taxReport/reducer.js b/src/state/taxReport/reducer.js index a74e73e90..3cf99620a 100644 --- a/src/state/taxReport/reducer.js +++ b/src/state/taxReport/reducer.js @@ -1,184 +1,82 @@ import authTypes from 'state/auth/constants' -import { - getFrameworkPositionsEntries, - getFrameworkPositionsTickersEntries, - getWalletsTickersEntries, - getWalletsEntries, -} from 'state/utils' -import { mapSymbol } from 'state/symbols/utils' -import timeRangeTypes from 'state/timeRange/constants' -import TAX_REPORT_SECTIONS from 'components/TaxReport/TaxReport.sections' import types from './constants' -const snapshotInitState = { - dataReceived: false, +const transactionsInitState = { + data: [], + progress: null, pageLoading: false, - positionsTotalPlUsd: null, - positionsEntries: [], - positionsTickersEntries: [], - walletsTotalBalanceUsd: null, - walletsTickersEntries: [], - walletsEntries: [], -} - -const finalResultInitState = { dataReceived: false, - pageLoading: false, - startingPositionsSnapshot: [], - endingPositionsSnapshot: [], - finalState: { - startingPeriodBalances: { - walletsTotalBalanceUsd: null, - positionsTotalPlUsd: null, - totalResult: null, - }, - movements: [], - movementsTotalAmount: null, - endingPeriodBalances: { - walletsTotalBalanceUsd: null, - positionsTotalPlUsd: null, - totalResult: null, - }, - totalResult: null, - }, + showDisclaimer: true, + strategy: types.STRATEGY_LIFO, } const initialState = { - startSnapshot: snapshotInitState, - endSnapshot: snapshotInitState, - ...finalResultInitState, -} - -const getMovementsEntries = entries => entries.map((entry) => { - const { - amount, - amountUsd, - currency, - currencyName, - destinationAddress, - fees, - id, - mtsStarted, - mtsUpdated, - status, - transactionId, - } = entry - - return { - amount, - amountUsd, - currency: mapSymbol(currency), - currencyName, - destinationAddress, - fees, - id, - mtsStarted, - mtsUpdated, - status, - transactionId, - } -}) - -const getSectionProperty = (section) => { - if (section === TAX_REPORT_SECTIONS.START_SNAPSHOT) { - return 'startSnapshot' - } - return 'endSnapshot' + transactions: transactionsInitState, } export function taxReportReducer(state = initialState, action) { const { type: actionType, payload } = action switch (actionType) { - case types.FETCH_TAX_REPORT: + case types.FETCH_TRANSACTIONS: return { ...state, - pageLoading: true, + transactions: { + pageLoading: true, + strategy: state.transactions.strategy, + showDisclaimer: state.transactions.showDisclaimer, + }, } - case types.FETCH_SNAPSHOT: { - const snapshotSection = payload === TAX_REPORT_SECTIONS.START_SNAPSHOT ? 'startSnapshot' : 'endSnapshot' - + case types.UPDATE_TRANSACTIONS: { return { ...state, - [snapshotSection]: { - ...state[snapshotSection], - pageLoading: true, + transactions: { + data: payload, + progress: null, + pageLoading: false, + dataReceived: true, + strategy: state.transactions.strategy, + showDisclaimer: state.transactions.showDisclaimer, }, } } - case types.UPDATE_TAX_REPORT: { - if (!payload) { - return { - ...state, - dataReceived: true, - pageLoading: false, - } - } - - const { - startingPositionsSnapshot, - endingPositionsSnapshot, - finalState: { - startingPeriodBalances, - movements, - movementsTotalAmount, - endingPeriodBalances, - totalResult, - }, - } = payload - + case types.SET_TRANSACTIONS_STRATEGY: { return { ...state, - dataReceived: true, - pageLoading: false, - endingPositionsSnapshot: getFrameworkPositionsEntries(endingPositionsSnapshot), - finalState: { - startingPeriodBalances: startingPeriodBalances || {}, - movements: getMovementsEntries(movements), - movementsTotalAmount, - endingPeriodBalances: endingPeriodBalances || {}, - totalResult, + transactions: { + ...state.transactions, + strategy: payload, }, - startingPositionsSnapshot: getFrameworkPositionsEntries(startingPositionsSnapshot), } } - case types.UPDATE_TAX_REPORT_SNAPSHOT: { - const { result, section } = payload - const snapshotSection = getSectionProperty(section) - if (!result) { - return { - ...state, - [snapshotSection]: { - ...state[snapshotSection], - dataReceived: true, - pageLoading: false, - }, - } + case types.SET_SHOW_DISCLAIMER: { + return { + ...state, + transactions: { + ...state.transactions, + showDisclaimer: payload, + }, } - - const { - positionsSnapshot = [], positionsTickers = [], walletsTickers = [], walletsSnapshot = [], - positionsTotalPlUsd, walletsTotalBalanceUsd, - } = result - + } + case types.SET_GENERATION_PROGRESS: { return { ...state, - [snapshotSection]: { - dataReceived: true, - pageLoading: false, - positionsTotalPlUsd, - positionsEntries: getFrameworkPositionsEntries(positionsSnapshot), - positionsTickersEntries: getFrameworkPositionsTickersEntries(positionsTickers), - walletsTotalBalanceUsd, - walletsTickersEntries: getWalletsTickersEntries(walletsTickers), - walletsEntries: getWalletsEntries(walletsSnapshot), + transactions: { + ...state.transactions, + progress: payload, }, } } case types.FETCH_FAIL: - return state - case types.REFRESH: - case timeRangeTypes.SET_TIME_RANGE: + return { + ...state, + transactions: { + ...state.transactions, + progress: null, + pageLoading: false, + dataReceived: true, + }, + } case authTypes.LOGOUT: return initialState default: { diff --git a/src/state/taxReport/saga.js b/src/state/taxReport/saga.js index bc8e17b8d..bfaf0c427 100644 --- a/src/state/taxReport/saga.js +++ b/src/state/taxReport/saga.js @@ -6,37 +6,28 @@ import { } from 'redux-saga/effects' import { makeFetchCall } from 'state/utils' -import { toggleErrorDialog } from 'state/ui/actions' -import { updateErrorStatus } from 'state/status/actions' +import { updateSuccessStatus, updateErrorStatus } from 'state/status/actions' import { getTimeFrame } from 'state/timeRange/selectors' -import TAX_REPORT_SECTIONS from 'components/TaxReport/TaxReport.sections' import types from './constants' import actions from './actions' +import { getTransactionsStrategy } from './selectors' -export const getReqTaxReport = (params) => { - const { start, end } = params - return makeFetchCall('getFullTaxReport', { start, end }) -} - -const getReqTaxReportSnapshot = (end) => { - const params = end ? { end } : {} - return makeFetchCall('getFullSnapshotReport', params) -} +export const getReqTaxReport = (params) => makeFetchCall('makeTrxTaxReportInBackground', params) -/* eslint-disable-next-line consistent-return */ export function* fetchTaxReport() { try { const { start, end } = yield select(getTimeFrame) - const { result, error } = yield call(getReqTaxReport, { - start, - end, - }) - - yield put(actions.updateTaxReport(result)) + const strategy = yield select(getTransactionsStrategy) + const params = { start, end, strategy } + const { error } = yield call(getReqTaxReport, params) if (error) { - yield put(toggleErrorDialog(true, error.message)) + yield put(actions.fetchFail({ + id: 'status.fail', + topic: 'taxreport.title', + detail: error?.message ?? JSON.stringify(error), + })) } } catch (fail) { yield put(actions.fetchFail({ @@ -47,48 +38,36 @@ export function* fetchTaxReport() { } } -/* eslint-disable-next-line consistent-return */ -function* fetchTaxReportSnapshot({ payload: section }) { - try { - const { start, end } = yield select(getTimeFrame) - const timestamp = (section === TAX_REPORT_SECTIONS.START_SNAPSHOT) - ? start - : end - - const { result, error } = yield call(getReqTaxReportSnapshot, timestamp) - - yield put(actions.updateTaxReportSnapshot({ result, section })) +function* fetchTaxReportFail({ payload }) { + yield put(updateErrorStatus(payload)) +} - if (error) { - yield put(toggleErrorDialog(true, error.message)) - } - } catch (fail) { +function* handleTaxTrxReportGenerationCompleted({ payload }) { + const { result, error } = payload + if (result) { + yield put(actions.updateTaxReportTransactions(result)) + yield put(updateSuccessStatus({ id: 'taxreport.generation.success' })) + } + if (error) { yield put(actions.fetchFail({ - id: 'status.request.error', + id: 'status.fail', topic: 'taxreport.title', - detail: JSON.stringify(fail), + detail: error?.message ?? JSON.stringify(error), })) } } -// fetch section that is currently open, others will fetch when opened -function* refreshTaxReport({ payload }) { - const { section } = payload - - if (section === TAX_REPORT_SECTIONS.RESULT) { - yield put(actions.fetchTaxReport()) - } else { - yield put(actions.fetchTaxReportSnapshot(section)) +function* handleTaxTrxReportGenerationProgress({ payload }) { + const { result } = payload + if (result) { + const { progress } = result + yield put(actions.setGenerationProgress(progress)) } } -function* fetchTaxReportFail({ payload }) { - yield put(updateErrorStatus(payload)) -} - export default function* taxReportSaga() { - yield takeLatest(types.FETCH_TAX_REPORT, fetchTaxReport) - yield takeLatest(types.FETCH_SNAPSHOT, fetchTaxReportSnapshot) - yield takeLatest(types.REFRESH, refreshTaxReport) yield takeLatest(types.FETCH_FAIL, fetchTaxReportFail) + yield takeLatest([types.FETCH_TRANSACTIONS], fetchTaxReport) + yield takeLatest(types.WS_TAX_TRANSACTION_REPORT_GENERATION_PROGRESS, handleTaxTrxReportGenerationProgress) + yield takeLatest(types.WS_TAX_TRANSACTION_REPORT_GENERATION_COMPLETED, handleTaxTrxReportGenerationCompleted) } diff --git a/src/state/taxReport/selectors.js b/src/state/taxReport/selectors.js index c0b0bdf93..3079b3630 100644 --- a/src/state/taxReport/selectors.js +++ b/src/state/taxReport/selectors.js @@ -1,49 +1,22 @@ -import TAX_REPORT_SECTIONS from 'components/TaxReport/TaxReport.sections' +import types from './constants' export const getTaxReport = state => state.taxReport -export const getStartSnapshot = state => getTaxReport(state).startSnapshot -export const getEndSnapshot = state => getTaxReport(state).endSnapshot +export const getTaxTransactions = state => getTaxReport(state).transactions -export const getDataReceived = state => getTaxReport(state).dataReceived -export const getPageLoading = state => getTaxReport(state).pageLoading - -export const getData = (state) => { - const { - startingPositionsSnapshot, - endingPositionsSnapshot, - finalState, - } = getTaxReport(state) - - return { - startingPositionsSnapshot, - endingPositionsSnapshot, - finalState, - } -} -export const getSnapshot = (state, section) => { - if (section === TAX_REPORT_SECTIONS.START_SNAPSHOT) { - return getStartSnapshot(state) - } - return getEndSnapshot(state) -} -export const getSnapshotDataReceived = (state, section) => { - if (section === TAX_REPORT_SECTIONS.START_SNAPSHOT) { - return getStartSnapshot(state).dataReceived - } - return getEndSnapshot(state).dataReceived -} -export const getSnapshotPageLoading = (state, section) => { - if (section === TAX_REPORT_SECTIONS.START_SNAPSHOT) { - return getStartSnapshot(state).pageLoading - } - return getEndSnapshot(state).pageLoading -} +export const getTransactionsData = state => getTaxTransactions(state)?.data ?? [] +export const getTransactionsPageLoading = state => getTaxTransactions(state)?.pageLoading ?? false +export const getTransactionsDataReceived = state => getTaxTransactions(state)?.dataReceived ?? false +export const getTransactionsStrategy = state => getTaxTransactions(state)?.strategy ?? types.STRATEGY_LIFO +export const getTransactionsShowDisclaimer = state => getTaxTransactions(state)?.showDisclaimer ?? false +export const getTransactionsGenerationProgress = state => getTaxTransactions(state)?.progress ?? null export default { - getDataReceived, - getPageLoading, - getSnapshot, - getSnapshotDataReceived, - getSnapshotPageLoading, getTaxReport, + getTaxTransactions, + getTransactionsData, + getTransactionsStrategy, + getTransactionsPageLoading, + getTransactionsDataReceived, + getTransactionsShowDisclaimer, + getTransactionsGenerationProgress, } diff --git a/src/styles/themes/_dark.scss b/src/styles/themes/_dark.scss index 339b4e887..b4114db57 100644 --- a/src/styles/themes/_dark.scss +++ b/src/styles/themes/_dark.scss @@ -59,9 +59,9 @@ --tableEvenBg: #0B1923; --tableScrollBg: #0c1a25; --tableScrollThumbBg: #2A3F4D; - --tableAmountPosColor: #00a27c; + --tableAmountPosColor: #03ca9b; --tableAmountNegColor: #f05359; - --tableAmountFractionPosColor: #03ca9b; + --tableAmountFractionPosColor: #00a27c; --tableAmountFractionNegColor: #d85f64; // toasts diff --git a/src/ui/DataTable/_DataTable.scss b/src/ui/DataTable/_DataTable.scss index 56d42de2d..96fa4107a 100644 --- a/src/ui/DataTable/_DataTable.scss +++ b/src/ui/DataTable/_DataTable.scss @@ -51,12 +51,15 @@ } .bitfinex-amount { + font-weight: 600; &.bitfinex-green-text > .bitfinex-amount-fraction { color: var(--tableAmountFractionPosColor); + font-weight: 400; } &.bitfinex-red-text > .bitfinex-amount-fraction { color: var(--tableAmountFractionNegColor); + font-weight: 400; } } diff --git a/src/ui/TaxStrategySelector/TaxStrategySelector.js b/src/ui/TaxStrategySelector/TaxStrategySelector.js new file mode 100644 index 000000000..3c02ea09d --- /dev/null +++ b/src/ui/TaxStrategySelector/TaxStrategySelector.js @@ -0,0 +1,32 @@ +import React, { useCallback } from 'react' +import { useDispatch, useSelector } from 'react-redux' + +import types from 'state/taxReport/constants' +import { setTransactionsStrategy } from 'state/taxReport/actions' +import { getTransactionsStrategy } from 'state/taxReport/selectors' + +import Select from 'ui/Select' + +const items = [ + { value: types.STRATEGY_LIFO, label: 'LIFO' }, + { value: types.STRATEGY_FIFO, label: 'FIFO' }, +] + +const TaxStrategySelector = () => { + const dispatch = useDispatch() + const strategy = useSelector(getTransactionsStrategy) + + const handleChange = useCallback((value) => { + dispatch(setTransactionsStrategy(value)) + }, [dispatch]) + + return ( +