import { useQuery } from '@apollo/react-hooks' import { makeStyles } from '@material-ui/core' import BigNumber from 'bignumber.js' import gql from 'graphql-tag' import { utils as coinUtils } from 'lamassu-coins' import * as R from 'ramda' import React, { useEffect, useState } from 'react' import { useHistory } from 'react-router-dom' import LogsDowloaderPopover from 'src/components/LogsDownloaderPopper' import SearchBox from 'src/components/SearchBox' import SearchFilter from 'src/components/SearchFilter' import Title from 'src/components/Title' import DataTable from 'src/components/tables/DataTable' import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg' import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg' import { ReactComponent as CustomerLinkIcon } from 'src/styling/icons/month arrows/right.svg' import { ReactComponent as CustomerLinkWhiteIcon } from 'src/styling/icons/month arrows/right_white.svg' import { errorColor } from 'src/styling/variables' import { formatDate } from 'src/utils/timezones' import DetailsRow from './DetailsCard' import { mainStyles } from './Transactions.styles' import { getStatus } from './helper' const useStyles = makeStyles(mainStyles) const NUM_LOG_RESULTS = 1000 const GET_DATA = gql` query getData { config } ` const GET_TRANSACTIONS_CSV = gql` query transactions( $simplified: Boolean $limit: Int $from: Date $until: Date $timezone: String ) { transactionsCsv( simplified: $simplified limit: $limit from: $from until: $until timezone: $timezone ) } ` const GET_TRANSACTION_FILTERS = gql` query filters { transactionFilters { type value } } ` const GET_TRANSACTIONS = gql` query transactions( $limit: Int $from: Date $until: Date $txClass: String $machineName: String $customerName: String $fiatCode: String $cryptoCode: String $toAddress: String $status: String ) { transactions( limit: $limit from: $from until: $until txClass: $txClass machineName: $machineName customerName: $customerName fiatCode: $fiatCode cryptoCode: $cryptoCode toAddress: $toAddress status: $status ) { id txClass txHash toAddress commissionPercentage expired machineName operatorCompleted sendConfirmed dispense hasError: error deviceId fiat cashInFee fiatCode cryptoAtoms cryptoCode toAddress created customerName customerIdCardData customerIdCardPhotoPath customerFrontCameraPath customerPhone discount customerId isAnonymous batched batchTime } } ` const getFiltersObj = filters => R.reduce((s, f) => ({ ...s, [f.type]: f.value }), {}, filters) const Transactions = () => { const classes = useStyles() const history = useHistory() const [filters, setFilters] = useState([]) const { data: filtersResponse, loading: filtersLoading } = useQuery( GET_TRANSACTION_FILTERS ) const [variables, setVariables] = useState({ limit: NUM_LOG_RESULTS }) const { data: txData, loading: transactionsLoading, refetch, startPolling, stopPolling } = useQuery(GET_TRANSACTIONS, { variables }) useEffect(() => { startPolling(10000) return stopPolling }) const txList = txData?.transactions ?? [] const { data: configResponse, configLoading } = useQuery(GET_DATA) const timezone = R.path(['config', 'locale_timezone'], configResponse) const redirect = customerId => { return history.push(`/compliance/customer/${customerId}`) } const formatCustomerName = customer => { const { firstName, lastName } = customer return `${R.o(R.toUpper, R.head)(firstName)}. ${lastName}` } const getCustomerDisplayName = tx => { if (tx.isAnonymous) return 'Anonymous' if (tx.customerName) return tx.customerName if (tx.customerIdCardData) return formatCustomerName(tx.customerIdCardData) return tx.customerPhone } const elements = [ { header: '', width: 32, size: 'sm', view: it => (it.txClass === 'cashOut' ? : ) }, { header: 'Machine', name: 'machineName', width: 160, size: 'sm', view: R.path(['machineName']) }, { header: 'Customer', width: 202, size: 'sm', view: it => (
{getCustomerDisplayName(it)}
{!it.isAnonymous && (
redirect(it.customerId)}> {it.hasError ? ( ) : ( )}
)}
) }, { header: 'Cash', width: 144, textAlign: 'right', size: 'sm', view: it => `${Number.parseFloat(it.fiat)} ${it.fiatCode}` }, { header: 'Crypto', width: 150, textAlign: 'right', size: 'sm', view: it => `${coinUtils.toUnit(new BigNumber(it.cryptoAtoms), it.cryptoCode)} ${ it.cryptoCode }` }, { header: 'Address', view: it => coinUtils.formatCryptoAddress(it.cryptoCode, it.toAddress), className: classes.overflowTd, size: 'sm', width: 140 }, { header: 'Date (UTC)', view: it => timezone && formatDate(it.created, timezone, 'yyyy-MM-dd HH:mm:ss'), textAlign: 'right', size: 'sm', width: 195 }, { header: 'Status', view: it => getStatus(it), textAlign: 'left', size: 'sm', width: 80 } ] const onFilterChange = filters => { const filtersObject = getFiltersObj(filters) setFilters(filters) setVariables({ limit: NUM_LOG_RESULTS, txClass: filtersObject.type, machineName: filtersObject.machine, customerName: filtersObject.customer, fiatCode: filtersObject.fiat, cryptoCode: filtersObject.crypto, toAddress: filtersObject.address, status: filtersObject.status }) refetch && refetch() } const onFilterDelete = filter => { const newFilters = R.filter( f => !R.whereEq(R.pick(['type', 'value'], f), filter) )(filters) setFilters(newFilters) const filtersObject = getFiltersObj(newFilters) setVariables({ limit: NUM_LOG_RESULTS, txClass: filtersObject.type, machineName: filtersObject.machine, customerName: filtersObject.customer, fiatCode: filtersObject.fiat, cryptoCode: filtersObject.crypto, toAddress: filtersObject.address, status: filtersObject.status }) refetch && refetch() } const deleteAllFilters = () => { setFilters([]) const filtersObject = getFiltersObj([]) setVariables({ limit: NUM_LOG_RESULTS, txClass: filtersObject.type, machineName: filtersObject.machine, customerName: filtersObject.customer, fiatCode: filtersObject.fiat, cryptoCode: filtersObject.crypto, toAddress: filtersObject.address, status: filtersObject.status }) refetch && refetch() } const filterOptions = R.path(['transactionFilters'])(filtersResponse) const loading = transactionsLoading || filtersLoading || configLoading const errorLabel = ( ) return ( <>
Transactions
{txList && (
R.path(['transactionsCsv'])(logs)} simplified timezone={timezone} args={{ timezone }} />
)}
Cash-in
Cash-out
{errorLabel} Transaction error
{filters.length > 0 && ( )} ) } export default Transactions