import React , { useEffect , useMemo , useState , } from 'react' ; import { either as Either } from 'fp-ts' ; import { pipe } from 'fp-ts/lib/function' ; import Mousetrap from 'mousetrap' ; import { BaseView } from '@components/BaseView' ; import { Result , toFailedResult , toSuccessfulData } from '@hooks/useCommand' ; import { runCommand } from '@modules/core' ; import { createErrorNotification } from '@modules/error_notification' ; import { AssetHistory , AssetHistoryArray , createEmptyAccountname , Reconciliation , Transaction , } from '@modules/types' ; import { DashboardAccountsLocation , DashboardCollectionsLocation , DashboardLocation , DashboardMonitorsLocation , } from '../../Routes' ; import { useGlobalNames , useGlobalState } from '../../State' ; import { Collections } from './Tabs/Collections' ; import { DetailsView } from './Tabs/Details' ; import { Monitors } from './Tabs/Monitors' ; export const DashboardView = ( ) => { const [ loading , setLoading ] = useState ( false ) ; const [ staging , setStaging ] = useState ( false ) ; const [ hideZero , setHideZero ] = useState ( 'all' ) ; const [ hideNamed , setHideNamed ] = useState ( false ) ; const [ hideReconciled , setHideReconciled ] = useState ( false ) ; const [ showDetails , setShowDetails ] = useState ( false ) ; const [ period , setPeriod ] = useState ( 'by tx' ) ; const [ cancel , setCancel ] = useState ( false ) ; const { currentAddress } = useGlobalState ( ) ; const { namesMap } = useGlobalNames ( ) ; const { totalRecords , setTotalRecords } = useGlobalState ( ) ; const { transactionsStatus , transactionsData : transactions , transactionsMeta , setTransactions , } = useGlobalState ( ) ; useEffect ( ( ) => { if ( transactionsStatus === 'fail' ) { createErrorNotification ( { description : 'Could not fetch transactions' , } ) ; } } , [ transactionsStatus ] ) ; useEffect ( ( ) => { let cancelled = false ; ( async ( ) => { if ( currentAddress ?. slice ( 0 , 2 ) === '0x' ) { const eitherResponse = await runCommand ( 'list' , { count : true , appearances : true , addrs : currentAddress , } ) ; if ( cancelled ) { return ; } const result = pipe ( eitherResponse , Either . fold ( toFailedResult , ( serverResponse ) => toSuccessfulData ( serverResponse ) as Result ) ) ; setTotalRecords ( result . status === 'success' ? result . data [ 0 ] ?. nRecords : 0 ) ; } } ) ( ) ; return ( ) => { cancelled = true ; } ; } , [ currentAddress , setTotalRecords ] ) ; useEffect ( ( ) => { let cancelled = false ; ( async ( ) => { const transactionCount = transactions . length ; if ( ! cancel && totalRecords && transactionCount < totalRecords ) { setLoading ( transactionCount < 10 ) ; const eitherResponse = await runCommand ( 'export' , { addrs : currentAddress || '' , fmt : 'json' , cache_txs : true , cache_traces : true , staging : false , unripe : false , ether : true , dollars : false , articulate : true , accounting : true , reversed : false , relevant : true , first_record : transactionCount , max_records : ( ( ) => { if ( transactionCount < 50 ) return 10 ; if ( transactionCount < 150 ) return 71 ; if ( transactionCount < 1500 ) return 239 ; return 639 ; } ) ( ) , } ) ; if ( cancelled ) { return ; } const result = pipe ( eitherResponse , Either . fold ( toFailedResult , ( serverResponse ) => toSuccessfulData ( serverResponse ) as Result ) ) ; setTransactions ( { result : toSuccessfulData ( { data : [ ... transactions , ... ( result . data as [ ] ) , ] , meta : transactionsMeta , } ) , loading : false , } ) ; setLoading ( false ) ; } } ) ( ) ; return ( ) => { cancelled = true ; } ; } , [ currentAddress , setTransactions , totalRecords , transactions , transactionsMeta , cancel ] ) ; useEffect ( ( ) => ( ) => { Mousetrap . unbind ( [ 'esc' ] ) ; } , [ ] ) ; Mousetrap . bind ( 'esc' , ( ) => setCancel ( true ) ) ; const transactionModels = useMemo ( ( ) => ( transactions as Transaction [ ] ) . map ( ( transaction , index ) => { const newId = String ( index + 1 ) ; const fromName = namesMap . get ( transaction . from ) || createEmptyAccountname ( ) ; const toName = namesMap . get ( transaction . to ) || createEmptyAccountname ( ) ; return { ... transaction , id : newId , fromName , toName , } ; } ) , [ namesMap , transactions ] ) ; const theData = useMemo ( ( ) => transactionModels . filter ( ( transaction ) => { if ( ! hideReconciled ) return true ; return transaction . statements . some ( ( { reconciled } ) => ! reconciled ) ; } ) , [ hideReconciled , transactionModels ] ) ; const uniqAssets = useMemo ( ( ) => { if ( ! theData . length ) return [ ] ; const unique = [ ] ; theData . forEach ( ( tx ) => { tx . statements ?. forEach ( ( statement ) => { if ( unique . find ( ( asset ) => asset . assetAddr === statement . assetAddr ) === undefined ) { unique . push ( { assetAddr : statement . assetAddr , assetSymbol : statement . assetSymbol , balHistory : [ ] , } ) ; } } ) ; unique . forEach ( ( asset , index ) => { const found = tx . statements ?. find ( ( statement ) => asset . assetAddr === statement . assetAddr ) ; if ( found ) { unique [ index ] . balHistory = [ ... unique [ index ] . balHistory , { balance : found . endBal , date : new Date ( found . timestamp * 1000 ) , reconciled : found . reconciled } , ] ; } } ) ; } ) ; unique . sort ( ( a , b ) => { if ( b . balHistory . length === a . balHistory . length ) { if ( b . balHistory . length === 0 ) { return b . assetAddr - a . assetAddr ; } return b . balHistory [ b . balHistory . length - 1 ] . balance - a . balHistory [ a . balHistory . length - 1 ] . balance ; } return b . balHistory . length - a . balHistory . length ; } ) ; return unique . filter ( ( asset ) => { if ( asset . balHistory . length === 0 ) return false ; const show = hideZero === 'all' || ( hideZero === 'show' && Number ( asset . balHistory [ asset . balHistory . length - 1 ] . balance ) === 0 ) || ( hideZero === 'hide' && Number ( asset . balHistory [ asset . balHistory . length - 1 ] . balance ) > 0 ) ; return show && ( ! hideNamed || ! namesMap . get ( asset . assetAddr ) ) ; } ) ; } , [ hideNamed , hideZero , namesMap , theData ] ) ; const params = { loading , setLoading , prefs : { staging , setStaging , hideZero , setHideZero , hideNamed , setHideNamed , hideReconciled , setHideReconciled , showDetails , setShowDetails , period , setPeriod , } , totalRecords , theData , setTransactions , theMeta : transactionsMeta , uniqAssets , } ; const tabs = [ { name : 'Monitors' , location : DashboardMonitorsLocation , component : < Monitors /> }, { name : 'Details' , location : DashboardAccountsLocation , component : < DetailsView params = { params } / > , } , { name : 'Collections' , location : DashboardCollectionsLocation , component : < Collections /> }, ] ; return ( < BaseView title = 'Dashboard' cookieName = 'COOKIE_DASHBOARD' tabs = { tabs } / > ) ; } ; declare type stateSetter < Type > = React . Dispatch < React . SetStateAction < Type > > ; export type UserPrefs = { staging : boolean ; setStaging : stateSetter < boolean > ; hideZero : string ; setHideZero : stateSetter < string > ; hideNamed : boolean ; setHideNamed : stateSetter < boolean > ; hideReconciled : boolean ; setHideReconciled : stateSetter < boolean > ; showDetails : boolean ; setShowDetails : stateSetter < boolean > ; period : string ; setPeriod : stateSetter < string > ; } ; export type AccountViewParams = { prefs : UserPrefs ; loading : boolean ; setLoading : stateSetter < boolean > ; totalRecords : number | null ; theData : any ; setTransactions : any ; theMeta : any ; uniqAssets : AssetHistoryArray ; } ;