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 (
+
+ )
+}
+
+export default TaxStrategySelector
diff --git a/src/ui/TaxStrategySelector/index.js b/src/ui/TaxStrategySelector/index.js
new file mode 100644
index 000000000..ba46fd20e
--- /dev/null
+++ b/src/ui/TaxStrategySelector/index.js
@@ -0,0 +1 @@
+export { default } from './TaxStrategySelector'
diff --git a/src/utils/columns.js b/src/utils/columns.js
index ac451707a..0b69a0396 100644
--- a/src/utils/columns.js
+++ b/src/utils/columns.js
@@ -6,6 +6,7 @@ import _head from 'lodash/head'
import _fill from 'lodash/fill'
import _floor from 'lodash/floor'
import _filter from 'lodash/filter'
+import _toLower from 'lodash/toLower'
import _forEach from 'lodash/forEach'
import { Cell } from '@blueprintjs/table'
import { get, pick, isEqual } from '@bitfinex/lib-js-util-base'
@@ -21,6 +22,7 @@ const COLUMN_WIDTH_STANDARD = {
amountUsd: 132,
amountExecuted: 132,
ask: 132,
+ asset: 92,
balance: 132,
balanceChange: 178,
balanceUsd: 137,
@@ -50,6 +52,7 @@ const COLUMN_WIDTH_STANDARD = {
fundBal: 205,
fundingAccrued: 185,
fundingStep: 155,
+ gainOrLoss: 152,
id: 95,
invoices: 160,
ip: 125,
@@ -64,9 +67,11 @@ const COLUMN_WIDTH_STANDARD = {
merchantName: 120,
mobile: 90,
mts: 150,
+ mtsAcquired: 150,
mtsCreate: 150,
mtsOpening: 150,
mtsLastPayout: 150,
+ mtsSold: 150,
mtsUpdate: 150,
mtsUpdated: 150,
note: 135,
@@ -82,6 +87,7 @@ const COLUMN_WIDTH_STANDARD = {
priceAvg: 132,
priceLiq: 132,
priceSpot: 132,
+ proceeds: 152,
priceTrailing: 132,
rate: 130,
redirectUrl: 300,
@@ -112,6 +118,7 @@ const COLUMN_WIDTHS_BIG_SCREENS = {
amountUsd: 160,
amountExecuted: 160,
ask: 160,
+ asset: 110,
balance: 160,
balanceChange: 178,
balanceUsd: 160,
@@ -141,6 +148,7 @@ const COLUMN_WIDTHS_BIG_SCREENS = {
fundBal: 205,
fundingAccrued: 185,
fundingStep: 155,
+ gainOrLoss: 160,
id: 140,
ip: 115,
invoices: 160,
@@ -155,9 +163,11 @@ const COLUMN_WIDTHS_BIG_SCREENS = {
merchantName: 140,
mobile: 90,
mts: 170,
+ mtsAcquired: 180,
mtsCreate: 180,
mtsOpening: 180,
mtsLastPayout: 180,
+ mtsSold: 180,
mtsUpdate: 180,
mtsUpdated: 180,
note: 250,
@@ -169,6 +179,7 @@ const COLUMN_WIDTHS_BIG_SCREENS = {
pl: 110,
plPerc: 110,
positionPair: 120,
+ proceeds: 160,
price: 160,
priceAvg: 160,
priceSpot: 160,
@@ -661,6 +672,8 @@ export const formatSumUpValue = value => {
return parseFloat(value).toFixed(8).replace(/\d(?=(\d{3})+\.)/g, '$&,')
}
+export const formatSourceType = (type, t) => t(`taxreport.sources.${_toLower(type)}`)
+
export const MIN_COLUMN_WIDTH = 125
export const WIDE_COLUMN_DEFAULT_WIDTH = 300
export const DEFAULT_CONTAINER_WIDTH = 1000
@@ -711,4 +724,5 @@ export default {
formatSumUpValue,
getTooltipContent,
getCalculatedColumnWidths,
+ formatSourceType,
}