diff --git a/public/locales/en/translations.json b/public/locales/en/translations.json
index 3a5b5765c..100050ca7 100644
--- a/public/locales/en/translations.json
+++ b/public/locales/en/translations.json
@@ -687,6 +687,13 @@
"min_balance_switch": "Minimum Balance",
"min_balance_input": "Set a minimum balance for assets to show up in the table."
}
+ },
+ "positions": {
+ "title": "Active Positions",
+ "derivative": "Derivative",
+ "margin": "Margin",
+ "position": "Position",
+ "amount": "Amount"
}
},
"symbols": {
diff --git a/src/components/AppSummary/AppSummary.columns.js b/src/components/AppSummary/AppSummary.columns.js
index 431b74814..d19c0c244 100644
--- a/src/components/AppSummary/AppSummary.columns.js
+++ b/src/components/AppSummary/AppSummary.columns.js
@@ -1,9 +1,10 @@
import React from 'react'
import { Cell } from '@blueprintjs/table'
-import { fixedFloat } from 'ui/utils'
+import { formatAmount, fixedFloat } from 'ui/utils'
import LoadingPlaceholder from 'ui/LoadingPlaceholder'
import {
+ getCell,
getCellLoader,
getCellNoData,
getColumnWidth,
@@ -12,10 +13,12 @@ import {
import {
getIsTotal,
+ getPairLabel,
formatUsdValue,
getFeePercentCell,
formatPercentValue,
formatUsdValueChange,
+ formatSecondaryPercentValue,
} from './AppSummary.helpers'
export const getFeesColumns = ({
@@ -302,3 +305,123 @@ export const getAssetColumns = ({
copyText: rowIndex => fixedFloat(preparedData[rowIndex]?.marginFundingPayment, 2),
},
]
+
+export const getPositionsColumns = ({
+ t,
+ isNoData,
+ isLoading,
+ entries,
+ columnsWidth,
+}) => [
+ {
+ id: 'pair',
+ name: 'column.pair',
+ className: 'align-left',
+ width: getColumnWidth('pair', columnsWidth),
+ renderer: (rowIndex) => {
+ if (isLoading) return getCellLoader(22, 80)
+ if (isNoData) return getCellNoData(t('column.noResults'))
+ const { pair, leverage } = entries[rowIndex]
+ const pairLabel = getPairLabel(t, pair, leverage)
+ return (
+
+ <>
+
+ {pair}
+
+
+
+ {pairLabel}
+
+ >
+ |
+ )
+ },
+ copyText: rowIndex => entries[rowIndex].pair,
+ },
+ {
+ id: 'amount',
+ name: 'column.amount',
+ width: getColumnWidth('amount', columnsWidth),
+ renderer: (rowIndex) => {
+ if (isLoading) return getCellLoader(22, 80)
+ if (isNoData) return getCellNoData()
+ const { amount, basePrice } = entries[rowIndex]
+ return (
+
+ <>
+
+ {fixedFloat(amount)}
+
+
+
+ @
+ {fixedFloat(basePrice)}
+
+ >
+ |
+ )
+ },
+ copyText: rowIndex => fixedFloat(entries[rowIndex].amount),
+ },
+ {
+ id: 'pl',
+ name: 'column.pl',
+ width: getColumnWidth('pl', columnsWidth),
+ renderer: (rowIndex) => {
+ if (isLoading) return getCellLoader(22, 80)
+ if (isNoData) return getCellNoData()
+ const { pl, plPerc } = entries[rowIndex]
+ return (
+
+ <>
+
+ {formatAmount(pl)}
+
+
+
+ {formatSecondaryPercentValue(plPerc)}
+
+ >
+ |
+ )
+ },
+ copyText: rowIndex => fixedFloat(entries[rowIndex].pl),
+ },
+ {
+ id: 'liquidationPrice',
+ name: 'column.liq-price',
+ width: getColumnWidth('liquidationPrice', columnsWidth),
+ renderer: (rowIndex) => {
+ if (isLoading) return getCellLoader(22, 80)
+ if (isNoData) return getCellNoData()
+ const { liquidationPrice } = entries[rowIndex]
+ return getCell(formatAmount(liquidationPrice, { color: 'red' }), t, fixedFloat(liquidationPrice))
+ },
+ copyText: rowIndex => fixedFloat(entries[rowIndex].liquidationPrice),
+ },
+ {
+ id: 'marginFunding',
+ name: 'column.fundingCost',
+ width: getColumnWidth('marginFunding', columnsWidth),
+ renderer: (rowIndex) => {
+ if (isLoading) return getCellLoader(22, 80)
+ if (isNoData) return getCellNoData()
+ const { marginFunding } = entries[rowIndex]
+ return getCell(fixedFloat(marginFunding), t)
+ },
+ copyText: rowIndex => fixedFloat(entries[rowIndex].marginFunding),
+ },
+ {
+ id: 'collateral',
+ name: 'column.collateral',
+ width: getColumnWidth('collateral', columnsWidth),
+ renderer: (rowIndex) => {
+ if (isLoading) return getCellLoader(22, 80)
+ if (isNoData) return getCellNoData()
+ const { collateral } = entries[rowIndex]
+ return getCell(`$${fixedFloat(collateral)}`, t)
+ },
+ copyText: rowIndex => fixedFloat(entries[rowIndex].collateral),
+ },
+]
diff --git a/src/components/AppSummary/AppSummary.container.js b/src/components/AppSummary/AppSummary.container.js
index 8d86f3856..661ea3261 100644
--- a/src/components/AppSummary/AppSummary.container.js
+++ b/src/components/AppSummary/AppSummary.container.js
@@ -13,6 +13,7 @@ import {
} from 'state/accountBalance/actions'
import { refresh as refreshSummaryByAsset } from 'state/summaryByAsset/actions'
import { refresh as refreshProfits } from 'state/profits/actions'
+import { refresh as refreshPositions } from 'state/positionsActive/actions'
import {
getData,
getPageLoading,
@@ -44,6 +45,7 @@ const mapDispatchToProps = {
setParams,
refreshBalance,
refreshProfits,
+ refreshPositions,
refreshSummaryByAsset,
}
diff --git a/src/components/AppSummary/AppSummary.helpers.js b/src/components/AppSummary/AppSummary.helpers.js
index aa285c26b..c8e975e18 100644
--- a/src/components/AppSummary/AppSummary.helpers.js
+++ b/src/components/AppSummary/AppSummary.helpers.js
@@ -1,5 +1,7 @@
import React from 'react'
import { Cell } from '@blueprintjs/table'
+import _isNil from 'lodash/isNil'
+import _endsWith from 'lodash/endsWith'
import LoadingPlaceholder from 'ui/LoadingPlaceholder'
import { fixedFloat, formatFee, formatThousands } from 'ui/utils'
@@ -60,3 +62,16 @@ export const getFeePercentCell = (isLoading, value) => (
)}
)
+
+export const formatSecondaryPercentValue = (value) => {
+ const val = prepareNumericValue(value)
+ if (val > 1) return {`${val}%`}
+ if (val < 1) return {`${val}%`}
+ return {`${val}%`}
+}
+
+export const getPairLabel = (t, pair, leverage) => {
+ if (_endsWith(pair, 'PERP')) return t('summary.positions.derivative')
+ if (_isNil(leverage)) return t('summary.positions.position')
+ return t('summary.positions.margin')
+}
diff --git a/src/components/AppSummary/AppSummary.js b/src/components/AppSummary/AppSummary.js
index c8465a53d..5d1a538f1 100644
--- a/src/components/AppSummary/AppSummary.js
+++ b/src/components/AppSummary/AppSummary.js
@@ -19,6 +19,7 @@ import Fees from './AppSummary.fees'
import Value from './AppSummary.value'
import Profits from './AppSummary.profits'
import ByAsset from './AppSummary.byAsset'
+import Positions from './AppSummary.positions'
const AppSummary = ({
t,
@@ -33,6 +34,7 @@ const AppSummary = ({
isSyncRequired,
refreshBalance,
refreshProfits,
+ refreshPositions,
currentTimeFrame,
refreshSummaryByAsset,
isUnrealizedProfitExcluded,
@@ -55,8 +57,9 @@ const AppSummary = ({
refresh()
refreshBalance()
refreshProfits()
+ refreshPositions()
refreshSummaryByAsset()
- }, [refresh, refreshBalance, refreshSummaryByAsset, refreshProfits])
+ }, [refresh, refreshBalance, refreshSummaryByAsset, refreshProfits, refreshPositions])
return (
+
{
+ const { t } = useTranslation()
+ const dispatch = useDispatch()
+ const entries = useSelector(getEntries)
+ const pageLoading = useSelector(getPageLoading)
+ const dataReceived = useSelector(getDataReceived)
+ const isFirstSync = useSelector(getIsFirstSyncing)
+ const isSyncRequired = useSelector(getIsSyncRequired)
+ const columnsWidth = useSelector((state) => getColumnsWidth(state, TYPE))
+ const isLoading = isFirstSync || (!dataReceived && pageLoading)
+ const isNoData = dataReceived && isEmpty(entries)
+ const tableClasses = classNames('summary-positions-table', {
+ 'empty-table': isNoData,
+ })
+
+ useEffect(() => {
+ if (!dataReceived && !pageLoading && !isSyncRequired) {
+ dispatch(fetchAPositions())
+ }
+ }, [dataReceived, pageLoading, isSyncRequired])
+
+
+ const columns = useMemo(
+ () => getPositionsColumns({
+ entries, t, isLoading, isNoData, columnsWidth,
+ }),
+ [entries, t, isLoading, isNoData, columnsWidth],
+ )
+
+ let showContent
+ if (isNoData) {
+ showContent = (
+
+ )
+ } else {
+ showContent = (
+
+ )
+ }
+
+ return (
+
+
+
+
+ {t('summary.positions.title')}
+
+
+
+ {showContent}
+
+ )
+}
+
+export default SummaryActivePositions
diff --git a/src/components/AppSummary/_AppSummary.scss b/src/components/AppSummary/_AppSummary.scss
index 7928313ed..97645cce1 100644
--- a/src/components/AppSummary/_AppSummary.scss
+++ b/src/components/AppSummary/_AppSummary.scss
@@ -282,6 +282,11 @@
.collapsed-table {
.secondary-value {
color: var(--color2);
+
+ &-left {
+ text-align: left;
+ color: var(--color2);
+ }
}
}
@@ -340,6 +345,56 @@
}
}
+ .summary-positions-table {
+ border-top: none;
+
+ .bp3-table-column-name {
+ font-size: 14px;
+
+ &-text {
+ padding: 0 5px 0 2px;
+ }
+ }
+
+ .bp3-table-cell {
+ font-size: 14px;
+ padding: 0 5px 0 2px;
+
+ &:nth-last-child(-n+6) {
+ box-shadow: none;
+ }
+ }
+
+ .cell-value {
+ width: 100%;
+ text-align: end;
+ display: inline-block;
+
+ &.secondary-value {
+ color: var(--color2);
+
+ &-left {
+ text-align: left;
+ color: var(--color2);
+ }
+ }
+ }
+
+ &.empty-table {
+ .bp3-table-cell {
+ &:nth-last-child(-n+6) {
+ box-shadow: inset 0 -1px 0 var(--tableBorder);
+ }
+
+ &:nth-last-child(6) {
+ .bp3-table-truncated-text {
+ font-weight: 400;
+ }
+ }
+ }
+ }
+ }
+
.app-summary-item-sub-title {
margin-bottom: 0;
}
@@ -457,6 +512,14 @@
}
}
+.percent-pos-value {
+ color: var(--tableAmountFractionPosColor);
+}
+
+.percent-neg-value {
+ color: var(--tableAmountFractionNegColor);
+}
+
@media screen and (max-width: 768px) {
.full-width-item .app-summary-item-sub-title {
margin-bottom: 15px;
diff --git a/src/state/query/constants.js b/src/state/query/constants.js
index 01b05f56c..6fea79570 100644
--- a/src/state/query/constants.js
+++ b/src/state/query/constants.js
@@ -34,6 +34,7 @@ export default {
MENU_WIN_LOSS: 'averagewinloss',
MENU_WEIGHTED_AVERAGES: 'weightedaverages',
SUMMARY_BY_ASSET: 'summarybyasset',
+ SUMMARY_POSITIONS: 'summarypositions',
TIME_TYPE_UTC: 'utc',
TIME_TYPE_LOCALTIME: 'local',
DEFAULT_QUERY_LIMIT: 500,