From 1b9f915083b5c442f0d912f20408c7d5e181923a Mon Sep 17 00:00:00 2001 From: frost ostrich Date: Mon, 13 Nov 2023 19:04:43 +1000 Subject: [PATCH] feat: various react table updates & mobile responsive --- .../components/InputSelect/InputSelect.tsx | 2 +- .../DappPages/Dashboard/Metrics/index.tsx | 10 +- .../Dashboard/Table/TxnDataTable.tsx | 121 +++++++----- .../Dashboard/Table/TxnHistoryTable.tsx | 182 ++++++++++++------ .../Core/DappPages/Dashboard/Table/index.tsx | 16 +- 5 files changed, 205 insertions(+), 126 deletions(-) diff --git a/apps/dapp/src/components/InputSelect/InputSelect.tsx b/apps/dapp/src/components/InputSelect/InputSelect.tsx index 377e79f4da..c3acc6241f 100644 --- a/apps/dapp/src/components/InputSelect/InputSelect.tsx +++ b/apps/dapp/src/components/InputSelect/InputSelect.tsx @@ -3,7 +3,7 @@ import Select from 'react-select'; import { theme } from 'styles/theme'; export type Option = { - value: string | number; + value: string | number | undefined; label: string; }; diff --git a/apps/dapp/src/components/Pages/Core/DappPages/Dashboard/Metrics/index.tsx b/apps/dapp/src/components/Pages/Core/DappPages/Dashboard/Metrics/index.tsx index 65a6b57829..41ceef2f30 100644 --- a/apps/dapp/src/components/Pages/Core/DappPages/Dashboard/Metrics/index.tsx +++ b/apps/dapp/src/components/Pages/Core/DappPages/Dashboard/Metrics/index.tsx @@ -1,5 +1,5 @@ import Loader from 'components/Loader/Loader'; -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useState, Fragment } from 'react'; import { useMediaQuery } from 'react-responsive'; import styled from 'styled-components'; import { queryPhone } from 'styles/breakpoints'; @@ -58,27 +58,27 @@ const DashboardMetrics = ({ dashboardType }: DashboardMetricsProps) => { <> {sourceData!.metrics.map((row, idx) => ( - <> + {row.map((metric, idx) => ( {metric.title} {metric.value} ))} - + ))} {sourceData!.smallMetrics.map((row, idx) => ( // TODO: The MobileMetricsContainer for small should be .. smaller - <> + {row.map((metric, idx) => ( {metric.title} {metric.value} ))} - + ))} diff --git a/apps/dapp/src/components/Pages/Core/DappPages/Dashboard/Table/TxnDataTable.tsx b/apps/dapp/src/components/Pages/Core/DappPages/Dashboard/Table/TxnDataTable.tsx index d9638915a6..07e2304cb6 100644 --- a/apps/dapp/src/components/Pages/Core/DappPages/Dashboard/Table/TxnDataTable.tsx +++ b/apps/dapp/src/components/Pages/Core/DappPages/Dashboard/Table/TxnDataTable.tsx @@ -6,6 +6,8 @@ import { StrategyKey } from '../hooks/use-dashboardv2-metrics'; import { ArrowButtonUpDown } from 'components/Pages/Ascend/components/Trade/styles'; import { TxHistoryTableHeader } from './TxnHistoryTable'; import { InputSelect } from 'components/InputSelect/InputSelect'; +import { useMediaQuery } from 'react-responsive'; +import { queryMinTablet } from 'styles/breakpoints'; export enum TxType { Borrow = 'Borrow', @@ -31,58 +33,65 @@ type Props = { export const TxnDataTable = (props: Props) => { const { dataSubset, dataLoading, dataRefetching, tableHeaders, updateTableHeadersOrder } = props; - + const isDesktop = useMediaQuery({ + query: queryMinTablet, + }); return ( - - - - {tableHeaders.map((h, i) => ( - - updateTableHeadersOrder(h)}> - {h.name} - {h.orderDesc !== undefined ? : } - - {h.filter && h.dropdownOptions && ( - - )} - - ))} - - - - {dataLoading ? ( - loadSkeletonRows(1, tableHeaders.length) - ) : !dataSubset || dataSubset.length === 0 ? ( - - No data available - - ) : ( - <> - {dataRefetching && loadSkeletonRows(1, tableHeaders.length)} - {dataSubset.map((row, index) => ( - - {row.date} - {row.type} - {row.strategy} - {row.token} - {row.amount} - - - {row.txHash.slice(0, 12) + '...'} - - - + + + + + {tableHeaders.map((h, i) => ( + + updateTableHeadersOrder(h)}> + {h.name} + {h.orderDesc !== undefined ? : } + + {h.rowFilter && ( + // div wrapper forces to re-render InputSelect if default value +
+ +
+ )} +
))} - - )} - -
+ + + + {dataLoading ? ( + loadSkeletonRows(1, tableHeaders.length) + ) : !dataSubset || dataSubset.length === 0 ? ( + + No data available + + ) : ( + <> + {dataRefetching && loadSkeletonRows(1, tableHeaders.length)} + {dataSubset.map((row, index) => ( + + {row.date} + {row.type} + {row.strategy} + {row.token} + {row.amount} + + + {row.txHash.slice(0, 12) + '...'} + + + + ))} + + )} + +
+ ); }; @@ -111,7 +120,6 @@ const TableHeader = styled.th` `; const HeaderRow = styled.tr` - overflow: hidden; white-space: nowrap; `; @@ -124,8 +132,16 @@ const EmptySpace = styled.p` width: 21px; `; -const DataTable = styled.table` +const ScrollContainer = styled.div` + min-height: 250px; + overflow-x: auto; +`; + +const DataTable = styled.table<{ isDesktop: boolean }>` border-collapse: collapse; + min-width: ${({ isDesktop }) => (isDesktop ? '' : '750px')}; + table-layout: ${({ isDesktop }) => (isDesktop ? 'fixed' : '')}; + width: ${({ isDesktop }) => (isDesktop ? '100%' : '')}; color: ${({ theme }) => theme.palette.brand}; `; @@ -150,4 +166,3 @@ const LinkStyled = styled.a` color: ${({ theme }) => theme.palette.brandDark}; } `; - diff --git a/apps/dapp/src/components/Pages/Core/DappPages/Dashboard/Table/TxnHistoryTable.tsx b/apps/dapp/src/components/Pages/Core/DappPages/Dashboard/Table/TxnHistoryTable.tsx index 7f245f3189..94928209b5 100644 --- a/apps/dapp/src/components/Pages/Core/DappPages/Dashboard/Table/TxnHistoryTable.tsx +++ b/apps/dapp/src/components/Pages/Core/DappPages/Dashboard/Table/TxnHistoryTable.tsx @@ -1,18 +1,25 @@ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; +import type * as CSS from 'csstype'; import styled from 'styled-components'; import { TxHistoryFilterType } from '.'; import { DashboardType } from '../DashboardContent'; import { format } from 'date-fns'; import { TableRow, TxType, TxnDataTable } from './TxnDataTable'; import { PaginationControl } from './PaginationControl'; -import { RowFilter, useTxHistory, useTxHistoryAvailableRows } from '../hooks/use-dashboardv2-txHistory'; +import { + RowFilter, + dashboardTypeToStrategyKey, + useTxHistory, + useTxHistoryAvailableRows, +} from '../hooks/use-dashboardv2-txHistory'; import { useDebouncedCallback } from 'use-debounce'; import { StrategyKey } from '../hooks/use-dashboardv2-metrics'; -import { SelectTempleDaoOptions } from 'components/InputSelect/InputSelect'; +import { Option, SelectTempleDaoOptions } from 'components/InputSelect/InputSelect'; type Props = { dashboardType: DashboardType; txFilter: TxHistoryFilterType; + selectedStrategy: StrategyKey; }; export enum TableHeaders { @@ -31,98 +38,147 @@ enum DebtToken { export type TxHistoryTableHeader = { name: TableHeaders; + width: CSS.Property.Width; orderDesc?: boolean; - filter?: (event: HTMLInputElement) => void; - dropdownOptions?: SelectTempleDaoOptions; -} + rowFilter?: { + filterFn: (event: HTMLInputElement) => void; + dropdownOptions: SelectTempleDaoOptions; + defaultValue: Option; + }; +}; -const TxnHistoryTable = ({ dashboardType, txFilter }: Props) => { +const TxnHistoryTable = (props: Props) => { + const { dashboardType, txFilter, selectedStrategy } = props; const [currentPage, setCurrentPage] = useState(1); const [rowsPerPage, setRowsPerPage] = useState(10); + const [blockNumber, setBlockNumber] = useState(0); const [rowFilter, setRowFilter] = useState({}); const [tableHeaders, setTableHeaders] = useState([ { name: TableHeaders.Date, orderDesc: true, + width: '32.64%', }, { name: TableHeaders.Type, orderDesc: undefined, - filter: useDebouncedCallback(async (event: HTMLInputElement) => { - setRowFilter(s => ({...s, type: event.value})); - }, 200), - // TODO: get the values programatically from subpgrah - dropdownOptions: [ - { label:'All', value: '' }, - { label: TxType.Borrow, value: TxType.Borrow }, - { label: TxType.Repay, value: TxType.Repay }, - ] + width: '9.95%', + rowFilter: { + filterFn: useDebouncedCallback(async (event: HTMLInputElement) => { + setRowFilter((s) => ({ ...s, type: event.value })); + }, 200), + // TODO: get dropdown values programatically, see https://github.com/TempleDAO/temple/pull/880#discussion_r1386151604 + dropdownOptions: [ + { label: 'All', value: undefined }, + { label: TxType.Borrow, value: TxType.Borrow }, + { label: TxType.Repay, value: TxType.Repay }, + ], + defaultValue: { label: 'All', value: undefined }, + }, }, { name: TableHeaders.Strategy, orderDesc: undefined, - filter: useDebouncedCallback(async (event: HTMLInputElement) => { - setRowFilter(s => ({...s, strategy: event.value})); - }, 200), - // TODO: get the values programatically from subpgrah - dropdownOptions: [ - { label:'All', value: '' }, - { label: StrategyKey.RAMOS, value: StrategyKey.RAMOS }, - { label: StrategyKey.TLC, value: StrategyKey.TLC }, - { label: StrategyKey.TEMPLEBASE, value: StrategyKey.TEMPLEBASE }, - { label: StrategyKey.DSRBASE, value: StrategyKey.DSRBASE }, - ] + width: '16.48%', + rowFilter: { + filterFn: useDebouncedCallback(async (event: HTMLInputElement) => { + setRowFilter((s) => ({ ...s, strategy: event.value })); + }, 200), + // TODO: get dropdown values programatically, see https://github.com/TempleDAO/temple/pull/880#discussion_r1386151604 + dropdownOptions: [ + { label: 'All', value: undefined }, + { label: StrategyKey.RAMOS, value: StrategyKey.RAMOS }, + { label: StrategyKey.TLC, value: StrategyKey.TLC }, + { label: StrategyKey.TEMPLEBASE, value: StrategyKey.TEMPLEBASE }, + { label: StrategyKey.DSRBASE, value: StrategyKey.DSRBASE }, + ], + defaultValue: { label: selectedStrategy, value: selectedStrategy }, + }, }, { name: TableHeaders.Token, orderDesc: undefined, - filter: useDebouncedCallback(async (event: HTMLInputElement) => { - setRowFilter(s => ({...s, token: event.value})); - }, 200), - // TODO: get the values programatically from subpgrah - dropdownOptions: [ - { label:'All', value: '' }, - { label: DebtToken.DAI, value: DebtToken.DAI }, - { label: DebtToken.TEMPLE, value: DebtToken.TEMPLE }, - ] + width: '10.87%', + rowFilter: { + filterFn: useDebouncedCallback(async (event: HTMLInputElement) => { + setRowFilter((s) => ({ ...s, token: event.value })); + }, 200), + // TODO: get dropdown values programatically, see https://github.com/TempleDAO/temple/pull/880#discussion_r1386151604 + dropdownOptions: [ + { label: 'All', value: undefined }, + { label: DebtToken.DAI, value: DebtToken.DAI }, + { label: DebtToken.TEMPLE, value: DebtToken.TEMPLE }, + ], + defaultValue: { label: 'All', value: undefined }, + }, }, { name: TableHeaders.Amount, orderDesc: undefined, + width: '12.93%', }, { name: TableHeaders.TxHash, orderDesc: undefined, + width: '17.13%', }, ]); - const updateTableHeadersOrder = (clickedHeader: TxHistoryTableHeader) => setTableHeaders(prevState => { - const newState = prevState.map((prevStateHeader)=>{ - if(prevStateHeader.name === clickedHeader.name){ - return { ...prevStateHeader, orderDesc: !prevStateHeader.orderDesc }; - } - else { - return { ...prevStateHeader, orderDesc: undefined}; - } + const updateTableHeadersOrder = (clickedHeader: TxHistoryTableHeader) => + setTableHeaders((prevState) => { + const newState = prevState.map((prevStateHeader) => { + if (prevStateHeader.name === clickedHeader.name) { + return { ...prevStateHeader, orderDesc: !prevStateHeader.orderDesc }; + } else { + return { ...prevStateHeader, orderDesc: undefined }; + } + }); + return newState; }); - return newState; - }); - + + useEffect(() => { + const selectedStrategy = dashboardTypeToStrategyKey(dashboardType); + setTableHeaders((prevState) => { + const newState = prevState.map((prevStateHeader) => { + if (prevStateHeader.name === TableHeaders.Strategy) { + // When user changes dashboard url: + // 1. reset page + setCurrentPage(1); + // 2. update table strategy dropdown default value + return { + ...prevStateHeader, + rowFilter: prevStateHeader.rowFilter && { + filterFn: prevStateHeader.rowFilter.filterFn, + dropdownOptions: prevStateHeader.rowFilter.dropdownOptions, + defaultValue: { label: selectedStrategy, value: selectedStrategy }, + }, + }; + } + return prevStateHeader; + }); + return newState; + }); + }, [dashboardType]); + const availableRows = useTxHistoryAvailableRows({ dashboardType, txFilter, - rowFilter + rowFilter, }); + // Only change the blockNumber when the page is refreshed + // it ensures consistency in subsequent pagination queries results + if (blockNumber === 0 && availableRows.data) setBlockNumber(availableRows.data.blockNumber); + const txHistory = useTxHistory({ dashboardType, txFilter, rowFilter, - offset: (currentPage -1) * rowsPerPage, + offset: (currentPage - 1) * rowsPerPage, limit: rowsPerPage, - blockNumber: availableRows.data?.blockNumber || 0, - tableHeaders + blockNumber, + tableHeaders, }); const isLoading = availableRows.isLoading || txHistory.isLoading; @@ -130,18 +186,18 @@ const TxnHistoryTable = ({ dashboardType, txFilter }: Props) => { // Fetch strategies tx data const dataToTable: TableRow[] | undefined = txHistory.data?.map((tx) => { - const amount = Number(Number(tx.amount).toFixed(2)); - return { - date: format(new Date(Number(tx.timestamp) * 1000), 'yyyy-MM-dd H:mm:ss O'), - type: tx.name, - strategy: tx.strategy.name, - token: tx.token.symbol, - amount: amount, - txHash: tx.hash, - }; - }); + const amount = Number(Number(tx.amount).toFixed(2)); + return { + date: format(new Date(Number(tx.timestamp) * 1000), 'yyyy-MM-dd H:mm:ss O'), + type: tx.name, + strategy: tx.strategy.name, + token: tx.token.symbol, + amount: amount, + txHash: tx.hash, + }; + }); - const totalPages = Math.ceil((availableRows.data?.totalRowCount || 0) / rowsPerPage); + const totalPages = Math.ceil((availableRows.data?.totalRowCount || 0) / rowsPerPage); return ( @@ -152,7 +208,7 @@ const TxnHistoryTable = ({ dashboardType, txFilter }: Props) => { setCurrentPage={setCurrentPage} setRowsPerPage={setRowsPerPage} /> - { const [txFilter, setTxFilter] = useState(TxHistoryFilterType.all); + const selectedStrategy = dashboardTypeToStrategyKey(dashboardType); + + const isDesktop = useMediaQuery({ + query: queryPhone, + }); return ( - + Transaction History setTxFilter(TxHistoryFilterType.lastweek)}> @@ -35,7 +43,7 @@ const DashboardTransactionHistory = ({ dashboardType }: DashboardTransactionHist - + ); @@ -63,9 +71,9 @@ const TransactionHistoryContent = styled.div` width: 100%; `; -const TransactionHistoryHeader = styled.div` +const TransactionHistoryHeader = styled.div<{isDesktop: boolean}>` display: flex; - flex-direction: row; + flex-direction: ${({isDesktop}) => isDesktop ? 'row' : 'column'}; justify-content: space-between; align-items: center; `;