From eb99622136b09ba8b117492be3f6f85a23e81965 Mon Sep 17 00:00:00 2001 From: Anthony Potdevin Date: Sun, 15 Nov 2020 13:13:38 +0100 Subject: [PATCH] =?UTF-8?q?refactor:=20=E2=99=BB=EF=B8=8F=20forward=20char?= =?UTF-8?q?ts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/forwards.tsx | 97 +++++----- src/components/table/index.tsx | 175 ++++++++++++++++++ src/views/forwards/context.tsx | 67 +++++++ src/views/forwards/index.tsx | 46 +++++ .../forwardReport/ForwardChannelReport.tsx | 160 +++++----------- .../forwardReport/ForwardReportTables.tsx | 97 ++++++++++ 6 files changed, 478 insertions(+), 164 deletions(-) create mode 100644 src/components/table/index.tsx create mode 100644 src/views/forwards/context.tsx create mode 100644 src/views/forwards/index.tsx create mode 100644 src/views/home/reports/forwardReport/ForwardReportTables.tsx diff --git a/pages/forwards.tsx b/pages/forwards.tsx index 37d45f6f..13a4a967 100644 --- a/pages/forwards.tsx +++ b/pages/forwards.tsx @@ -1,88 +1,81 @@ -import React, { useState } from 'react'; -import { toast } from 'react-toastify'; import { GridWrapper } from 'src/components/gridWrapper/GridWrapper'; -import { Forward } from 'src/graphql/types'; import { NextPageContext } from 'next'; import { getProps } from 'src/utils/ssr'; -import { useGetForwardsPastDaysQuery } from 'src/graphql/queries/__generated__/getForwardsPastDays.generated'; import { MultiButton, SingleButton, } from 'src/components/buttons/multiButton/MultiButton'; +import { ForwardsList } from 'src/views/forwards/index'; +import { + ForwardProvider, + useForwardDispatch, + useForwardState, +} from 'src/views/forwards/context'; +import { + ForwardReport, + ReportType, +} from 'src/views/home/reports/forwardReport/ForwardReport'; +import { ForwardChannelsReport } from 'src/views/home/reports/forwardReport/ForwardChannelReport'; import { SubTitle, Card, CardWithTitle, CardTitle, + Separation, + SingleLine, } from '../src/components/generic/Styled'; -import { getErrorContent } from '../src/utils/error'; -import { LoadingCard } from '../src/components/loading/LoadingCard'; -import { ForwardCard } from '../src/views/forwards/ForwardsCard'; -import { ForwardBox } from '../src/views/home/reports/forwardReport'; const ForwardsView = () => { - const [time, setTime] = useState(30); - const [indexOpen, setIndexOpen] = useState(0); - - const { loading, data } = useGetForwardsPastDaysQuery({ - variables: { days: time }, - onError: error => toast.error(getErrorContent(error)), - }); - - if (loading || !data || !data.getForwardsPastDays) { - return ( - <> - - - - ); - } + const { days, infoType } = useForwardState(); + const dispatch = useForwardDispatch(); const renderButton = (selectedTime: number, title: string) => ( setTime(selectedTime)} + selected={selectedTime === days} + onClick={() => dispatch({ type: 'day', days: selectedTime })} > {title} ); - const renderNoForwards = () => ( - -

{`Your node has not forwarded any payments in the past ${time} ${ - time > 1 ? 'days' : 'day' - }.`}

-
+ const renderTypeButton = (type: ReportType, title: string) => ( + dispatch({ type: 'infoType', infoType: type })} + > + {title} + ); return ( <> - Forwards - + + + {renderButton(1, 'D')} {renderButton(7, '1W')} {renderButton(30, '1M')} {renderButton(90, '3M')} + {renderButton(180, '6M')} + {renderButton(360, '1Y')} - - {data?.getForwardsPastDays?.length ? ( - - {data?.getForwardsPastDays?.map((forward, index) => ( - - ))} - - ) : ( - renderNoForwards() - )} + + {renderTypeButton('amount', 'Amount')} + {renderTypeButton('tokens', 'Tokens')} + {renderTypeButton('fee', 'Fees')} + + + + + + + + + + ); @@ -90,7 +83,9 @@ const ForwardsView = () => { const Wrapped = () => ( - + + + ); diff --git a/src/components/table/index.tsx b/src/components/table/index.tsx new file mode 100644 index 00000000..1363dc40 --- /dev/null +++ b/src/components/table/index.tsx @@ -0,0 +1,175 @@ +import { useMemo, useState } from 'react'; +import styled, { css } from 'styled-components'; +import { + useTable, + useSortBy, + useAsyncDebounce, + useGlobalFilter, +} from 'react-table'; +import { separationColor } from 'src/styles/Themes'; +import { Input } from '../input'; + +type StyledTableProps = { + withBorder?: boolean; + alignCenter?: boolean; + fontSize?: string; +}; + +const Styles = styled.div` + overflow-x: auto; + table { + border-spacing: 0; + tr { + :last-child { + td { + border-bottom: 0; + } + } + } + th, + td { + font-size: ${({ fontSize }: StyledTableProps) => fontSize || '14px'}; + text-align: left; + margin: 0; + padding: 8px; + ${({ withBorder }: StyledTableProps) => + withBorder && + css` + border-bottom: 1px solid ${separationColor}; + `} + ${({ alignCenter }: StyledTableProps) => + alignCenter && + css` + text-align: center; + padding: 8px; + `} + :last-child { + border-right: 0; + } + } + } +`; + +const FilterLine = styled.div` + margin-bottom: 24px; +`; + +const GlobalFilter = ({ + preGlobalFilteredRows, + globalFilter, + setGlobalFilter, + filterPlaceholder, +}: any) => { + const count = preGlobalFilteredRows.length; + const [value, setValue] = useState(globalFilter); + const onChange = useAsyncDebounce(value => { + setGlobalFilter(value || undefined); + }, 200); + + return ( + + { + setValue(e.target.value); + onChange(e.target.value); + }} + placeholder={`Search ${count} ${filterPlaceholder || ''}`} + /> + + ); +}; + +type TableProps = { + tableData: any[]; + tableColumns: + | { Header: string; accessor: string }[] + | { Header: string; columns: { Header: string; accessor: string }[] }[]; + withBorder?: boolean; + fontSize?: string; + filterPlaceholder?: string; + notSortable?: boolean; +}; + +export const Table: React.FC = ({ + tableData, + tableColumns, + withBorder, + fontSize, + filterPlaceholder, + notSortable, +}) => { + const data = useMemo(() => tableData, [tableData]); + const columns = useMemo(() => tableColumns, [tableColumns]); + + const { + getTableProps, + getTableBodyProps, + headerGroups, + rows, + prepareRow, + state, + preGlobalFilteredRows, + setGlobalFilter, + } = useTable( + { + columns, + data, + }, + useGlobalFilter, + useSortBy + ); + + return ( + <> + {!!filterPlaceholder && ( + + )} + + + + {headerGroups.map((headerGroup, index) => ( + + {headerGroup.headers.map((column, index) => ( + + ))} + + ))} + + + {rows.map((row, index) => { + prepareRow(row); + return ( + + {row.cells.map((cell, index) => { + return ( + + ); + })} + + ); + })} + +
+ {column.render('Header')} + + {column.isSorted ? (column.isSortedDesc ? '⬇' : '⬆') : ''} + +
+ {cell.render('Cell')} +
+
+ + ); +}; diff --git a/src/views/forwards/context.tsx b/src/views/forwards/context.tsx new file mode 100644 index 00000000..bf98fdf6 --- /dev/null +++ b/src/views/forwards/context.tsx @@ -0,0 +1,67 @@ +import { FC, createContext, useContext, useReducer } from 'react'; + +type ReportType = 'fee' | 'tokens' | 'amount'; + +type State = { + days: number; + infoType: ReportType; +}; + +type ActionType = + | { + type: 'day'; + days: number; + } + | { + type: 'infoType'; + infoType: ReportType; + }; + +type Dispatch = (action: ActionType) => void; + +export const StateContext = createContext(undefined); +export const DispatchContext = createContext(undefined); + +const initialState: State = { + days: 30, + infoType: 'amount', +}; + +const stateReducer = (state: State, action: ActionType): State => { + switch (action.type) { + case 'day': + return { ...state, days: action.days }; + case 'infoType': + return { ...state, infoType: action.infoType }; + default: + return state; + } +}; + +const ForwardProvider: FC = ({ children }) => { + const [state, dispatch] = useReducer(stateReducer, initialState); + + return ( + + {children} + + ); +}; + +const useForwardState = () => { + const context = useContext(StateContext); + if (context === undefined) { + throw new Error('useForwardState must be used within a ForwardProvider'); + } + return context; +}; + +const useForwardDispatch = () => { + const context = useContext(DispatchContext); + if (context === undefined) { + throw new Error('useForwardDispatch must be used within a ForwardProvider'); + } + return context; +}; + +export { ForwardProvider, useForwardState, useForwardDispatch }; diff --git a/src/views/forwards/index.tsx b/src/views/forwards/index.tsx new file mode 100644 index 00000000..6694b07b --- /dev/null +++ b/src/views/forwards/index.tsx @@ -0,0 +1,46 @@ +import { FC, useState } from 'react'; +import { toast } from 'react-toastify'; +import { LoadingCard } from 'src/components/loading/LoadingCard'; +import { useGetForwardsPastDaysQuery } from 'src/graphql/queries/__generated__/getForwardsPastDays.generated'; +import { Forward } from 'src/graphql/types'; +import { getErrorContent } from 'src/utils/error'; +import { ForwardCard } from './ForwardsCard'; + +type ForwardProps = { + days: number; +}; + +export const ForwardsList: FC = ({ days }) => { + const [indexOpen, setIndexOpen] = useState(0); + + const { loading, data } = useGetForwardsPastDaysQuery({ + variables: { days }, + onError: error => toast.error(getErrorContent(error)), + }); + + if (loading) { + return ; + } + + if (!data?.getForwardsPastDays?.length) { + return ( +

{`Your node has not forwarded any payments in the past ${days} ${ + days > 1 ? 'days' : 'day' + }.`}

+ ); + } + + return ( + <> + {data?.getForwardsPastDays?.map((forward, index) => ( + + ))} + + ); +}; diff --git a/src/views/home/reports/forwardReport/ForwardChannelReport.tsx b/src/views/home/reports/forwardReport/ForwardChannelReport.tsx index b57876bd..4460c33e 100644 --- a/src/views/home/reports/forwardReport/ForwardChannelReport.tsx +++ b/src/views/home/reports/forwardReport/ForwardChannelReport.tsx @@ -1,45 +1,21 @@ import React, { useState } from 'react'; import { toast } from 'react-toastify'; import { GitCommit, ArrowDown, ArrowUp } from 'react-feather'; -import styled from 'styled-components'; import { MultiButton, SingleButton, } from 'src/components/buttons/multiButton/MultiButton'; import { useGetForwardsPastDaysQuery } from 'src/graphql/queries/__generated__/getForwardsPastDays.generated'; import { Forward } from 'src/graphql/types'; +import styled from 'styled-components'; import { getErrorContent } from '../../../../utils/error'; -import { - DarkSubTitle, - SingleLine, -} from '../../../../components/generic/Styled'; +import { SingleLine, SubTitle } from '../../../../components/generic/Styled'; import { LoadingCard } from '../../../../components/loading/LoadingCard'; -import { getPrice } from '../../../../components/price/Price'; -import { useConfigState } from '../../../../context/ConfigContext'; -import { usePriceState } from '../../../../context/PriceContext'; import { ReportType } from './ForwardReport'; import { orderForwardChannels } from './helpers'; +import { ChannelTable, RouteTable } from './ForwardReportTables'; import { CardContent } from '.'; -const ChannelRow = styled.div` - font-size: 14px; - display: flex; - justify-content: space-between; - align-items: center; -`; - -const TableLine = styled.div` - width: 35%; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -`; - -const LastTableLine = styled(TableLine)` - width: auto; - text-align: right; -`; - type Props = { days: number; order: ReportType; @@ -62,13 +38,13 @@ type ParsedChannelType = { amount: number; }; +const Spacing = styled.div` + margin-bottom: 16px; +`; + export const ForwardChannelsReport = ({ days, order }: Props) => { const [type, setType] = useState<'route' | 'incoming' | 'outgoing'>('route'); - const { currency, displayValues } = useConfigState(); - const priceContext = usePriceState(); - const format = getPrice(currency, displayValues, priceContext); - const { data, loading } = useGetForwardsPastDaysQuery({ ssr: false, variables: { days }, @@ -85,96 +61,54 @@ export const ForwardChannelsReport = ({ days, order }: Props) => { data.getForwardsPastDays as Forward[] ); - const getFormatString = (amount: number | string) => { - if (typeof amount === 'string') return amount; - if (order !== 'amount') { - return format({ amount }); - } - return amount; - }; - - const renderRoute = (parsed: ParsedRouteType[]) => { - const routes = parsed.map((channel: ParsedRouteType, index) => ( - - {channel.aliasIn} - {channel.aliasOut} - {getFormatString(channel[order])} - - )); - - return ( - <> - - Incoming - Outgoing - - - {routes} - - ); - }; - - const renderChannels = (parsed: ParsedChannelType[]) => { - const channels = parsed.map((channel: ParsedChannelType, index) => ( - - {`${channel.alias}`} - {`${channel.name}`} - {getFormatString(channel[order])} - - )); - - return ( - <> - - Alias - ID - - - {channels} - - ); - }; - const renderContent = (parsed: (ParsedChannelType | ParsedRouteType)[]) => { switch (type) { case 'route': - return renderRoute(parsed as ParsedRouteType[]); + return ( + + ); default: - return renderChannels(parsed as ParsedChannelType[]); + return ( + + ); } }; - const renderButtons = () => ( - - setType('incoming')} - > - - - setType('route')} - > - - - setType('outgoing')} - > - - - - ); - const renderTop = (title: string) => ( - - {title} - {renderButtons()} - + + + {title} + + setType('incoming')} + > + + + setType('route')} + > + + + setType('outgoing')} + > + + + + + ); const renderTitle = () => { diff --git a/src/views/home/reports/forwardReport/ForwardReportTables.tsx b/src/views/home/reports/forwardReport/ForwardReportTables.tsx new file mode 100644 index 00000000..13ea1127 --- /dev/null +++ b/src/views/home/reports/forwardReport/ForwardReportTables.tsx @@ -0,0 +1,97 @@ +import { FC } from 'react'; +import { Table } from 'src/components/table'; +import { ReportType } from './ForwardReport'; + +type RouteType = { + route: string; + aliasIn: string; + aliasOut: string; + fee: number; + tokens: number; + amount: number; +}; + +type ChannelType = { + alias: string; + name: string; + fee: number; + tokens: number; + amount: number; +}; + +type RouteTableProps = { + order: ReportType; + forwardArray: RouteType[]; +}; + +type ChannelTableProps = { + order: ReportType; + forwardArray: ChannelType[]; +}; + +export const RouteTable: FC = ({ order, forwardArray }) => { + const getTitle = () => { + switch (order) { + case 'fee': + return 'Fee (sats)'; + case 'tokens': + return 'Tokens (sats)'; + default: + return 'Amount'; + } + }; + + const getAccesor = () => { + switch (order) { + case 'fee': + return 'fee'; + case 'tokens': + return 'tokens'; + default: + return 'amount'; + } + }; + + const columns = [ + { Header: 'In', accessor: 'aliasIn' }, + { Header: 'Out', accessor: 'aliasOut' }, + { Header: getTitle(), accessor: getAccesor() }, + ]; + + return ; +}; + +export const ChannelTable: FC = ({ + order, + forwardArray, +}) => { + const getTitle = () => { + switch (order) { + case 'fee': + return 'Fee (sats)'; + case 'tokens': + return 'Tokens (sats)'; + default: + return 'Amount'; + } + }; + + const getAccesor = () => { + switch (order) { + case 'fee': + return 'fee'; + case 'tokens': + return 'tokens'; + default: + return 'amount'; + } + }; + + const columns = [ + { Header: 'Alias', accessor: 'alias' }, + { Header: 'Id', accessor: 'name' }, + { Header: getTitle(), accessor: getAccesor() }, + ]; + + return
; +};