Skip to content

Commit

Permalink
Feature/add accounting page (#25)
Browse files Browse the repository at this point in the history
* feat(golden-ledger): add remote graphql schemas

- smart-escrow xdai
- dahouse stats xdai

* fix _meta collision with prefix

* chore(hasura): add docker auto-setup

- switch to auto metadata/migration image
- add setup container to auto-seed

* fix(hasura): reload metadata after seeding data

* chore(docker): update readme

* Add TRANSACTIONS_QUERY

* Update caniuse

* Add useTransactions

* Add data formatter for useTransactions

* Add Accounting page

* feat(tx-table): initial add

* refactor(tx-table): remove chakra-ui/icons dep

* fix(tx-table): fix column sorting

* feat(tx-table): add 'days held' column

* fix(SiteLayout.tsx): show {error.message}

* fix(accounting): query hasura

* Minor: add useBalances hook (#32)

* Add moloch query

* Add useBalances hook

* Add namespace for daohaus_xdai

* Pass transactions and balances as SiteLayout data

* chore(hasura-setup): cut setup timeout to 1m

* feat(accounting): add balances table

* treasury_token_history table added (#33)

Co-authored-by: Peter Sparacino <[email protected]>

* Add useTokenPrices hook

* txnID and symbol columns added (#34)

* Minor: add price data to transactions table (#35)

* Add price data to transactions table

* Return "unknown value" if no price conversion

* Fix typo

* Add tabs and cell borders to accounting page (#36)

* styling(accounting): style tab title

* accounting page: add table filters (#37)

* feat(accounting): add column filters (wip)

* feat(table-filtering): fix date/number filter

* fix(accounting): days-held filter

* fix(accounting): update table style

* feat(table-filtering): add enum type, tooltips

* Don't get balances unless transactions are fetched

* Minor tweaks

* Tweak useAccounting error messages

Co-authored-by: ECWireless <[email protected]>

* Add pagination for DataTable (#40)

* Add pagination for DataTable

* Make input label dynamic

* Accounting: link DAOhaus member profile from transactions table (#39)

* feat(accounting): add column filters (wip)

* feat(table-filtering): fix date/number filter

* fix(accounting): days-held filter

* fix(accounting): update table style

* fixes (#38)

* feat(table-filtering): add enum type, tooltips

* feat(accounting): tx-table - add member link (wip)

* fix(member-links): add daohaus profile link

* feat(accounting): link DAOhaus member profile

* refactor(BalancesTable): add aria-label

* fix(chains): jsonRpcProvider setup post lib update

* refactor(useMemberList): remove console.log

* Remove unnecessary comment

Co-authored-by: scottrepreneur <[email protected]>
Co-authored-by: ECWireless <[email protected]>

* Minor: improve accounting export data (#41)

* Improve accounting error messages

* Add accounting link to navbar

* Improve export data formatting

* Feature/add accounting page add current prices table (#42)

* add current_token_prices table

* updated field name

* Improve accounting page loader (#43)

Co-authored-by: Matthew O'Connell <[email protected]>
Co-authored-by: psparacino <[email protected]>
Co-authored-by: ECWireless <[email protected]>
Co-authored-by: ECWireless <[email protected]>
Co-authored-by: Peter Sparacino <[email protected]>
Co-authored-by: Matthew O'Connell <[email protected]>
  • Loading branch information
7 people authored Jan 21, 2023
1 parent 85db615 commit 28752f9
Show file tree
Hide file tree
Showing 44 changed files with 3,578 additions and 2,117 deletions.
88 changes: 88 additions & 0 deletions apps/frontend/components/BalancesTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { Link, Tooltip } from '@raidguild/design-system';
import { createColumnHelper } from '@tanstack/react-table';
import { ITokenBalanceLineItem } from '../types';
import { formatNumber, minMaxNumberFilter, sortNumeric } from '../utils';
import { DataTable } from './DataTable';
import TokenWithUsdValue from './TokenWithUsdValue';

interface BalancesTableProps {
data: ITokenBalanceLineItem[];
}

const columnHelper = createColumnHelper<ITokenBalanceLineItem>();

const columns = [
columnHelper.accessor('tokenExplorerLink', {
id: 'tokenExplorerLink',
cell: (info) => info.getValue(),
header: 'Token Link',
meta: {
hidden: true,
},
}),
columnHelper.accessor('token.symbol', {
id: 'tokenSymbol',
cell: (info) => (
<Link
href={info.row.getValue('tokenExplorerLink')}
target='_blank'
aria-label='tokenExplorerLink'
>
<Tooltip label='view token'>{info.getValue()}</Tooltip>
</Link>
),
header: 'Token',
meta: {
dataType: 'enum',
},
}),
columnHelper.accessor('inflow.tokenValue', {
cell: formatNumber,
header: 'Inflow',
meta: {
dataType: 'numeric',
},
filterFn: minMaxNumberFilter,
sortingFn: sortNumeric,
}),
columnHelper.accessor('outflow.tokenValue', {
cell: formatNumber,
header: 'Outflow',
meta: {
dataType: 'numeric',
},
filterFn: minMaxNumberFilter,
sortingFn: sortNumeric,
}),
columnHelper.accessor('priceConversion', {
id: 'priceConversion',
cell: (info) => info.getValue(),
header: 'Conversion',
meta: {
dataType: 'numeric',
hidden: true,
},
filterFn: minMaxNumberFilter,
sortingFn: sortNumeric,
}),
columnHelper.accessor('closing.tokenValue', {
cell: (info) => <TokenWithUsdValue info={info} />,
header: 'Balance',
meta: {
dataType: 'numeric',
},
filterFn: minMaxNumberFilter,
sortingFn: sortNumeric,
}),
];

const BalancesTable = ({ data }: BalancesTableProps) => (
<DataTable
id='balancesDataTable'
columns={columns}
data={data}
sort={[{ id: 'tokenSymbol', desc: false }]}
/>
);

export default BalancesTable;
231 changes: 231 additions & 0 deletions apps/frontend/components/DataTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
import { useState } from 'react';
import {
Box,
Input,
Select,
Table,
Tbody,
Td,
Th,
Thead,
ThemingProps,
Tr,
} from '@chakra-ui/react';
import { FiChevronDown, FiChevronUp } from 'react-icons/fi';
import {
useReactTable,
flexRender,
ColumnFiltersState,
getCoreRowModel,
getFilteredRowModel,
ColumnDef,
SortingState,
getSortedRowModel,
RowData,
getFacetedRowModel,
getFacetedMinMaxValues,
getFacetedUniqueValues,
getPaginationRowModel,
} from '@tanstack/react-table';
import Filter from './Filter';
import { Flex, Button, Text, TableContainer } from '@raidguild/design-system';

declare module '@tanstack/table-core' {
interface ColumnMeta<TData extends RowData, TValue> {
dataType?: 'numeric' | 'datetime' | 'enum' | 'string';
hidden?: boolean;
}
}

export type DataTableProps<Data extends object> = ThemingProps & {
id: string;
data: Data[];
columns: ColumnDef<Data, unknown>[];
sort?: SortingState;
};

export function DataTable<Data extends object>({
id,
data,
columns,
sort = [],
...props
}: DataTableProps<Data>) {
const [sorting, setSorting] = useState<SortingState>(sort);
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
const [columnVisibility, setColumnVisibility] = useState(
Object.assign(
{},
...columns.map((c) => ({ [c.id]: !c.meta?.hidden ?? true }))
)
);

const table = useReactTable({
columns,
data,
getCoreRowModel: getCoreRowModel(),
getFacetedRowModel: getFacetedRowModel(),
getFacetedUniqueValues: getFacetedUniqueValues(),
getFacetedMinMaxValues: getFacetedMinMaxValues(),
getFilteredRowModel: getFilteredRowModel(),
onSortingChange: setSorting,
getSortedRowModel: getSortedRowModel(),
onColumnFiltersChange: setColumnFilters,
onColumnVisibilityChange: setColumnVisibility,
state: {
columnFilters,
columnVisibility,
sorting,
},
initialState: {
pagination: {
pageSize: 20,
pageIndex: 0,
},
},
getPaginationRowModel: getPaginationRowModel(),
// debugTable: true,
// debugHeaders: true,
// debugColumns: true,
});

return (
<Box border='1px solid grey' borderRadius='4px'>
<TableContainer maxWidth='90vw'>
<Table id={id} {...props}>
<Thead>
{table.getHeaderGroups().map((headerGroup) => (
<Tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<Th
key={header.id}
isNumeric={
header.column.columnDef.meta?.dataType === 'numeric'
}
verticalAlign='top'
borderBlock='1px solid gray'
>
<Flex
justifyContent='space-between'
verticalAlign='middle'
onClick={header.column.getToggleSortingHandler()}
_hover={{ cursor: 'pointer' }}
>
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
{header.column.getIsSorted() ? (
header.column.getIsSorted() === 'desc' ? (
<FiChevronDown aria-label='sorted descending' />
) : (
<FiChevronUp aria-label='sorted ascending' />
)
) : null}
</Flex>
{header.column.getCanFilter() ? (
<Box my='1'>
<Filter column={header.column} />
</Box>
) : null}
</Th>
))}
</Tr>
))}
</Thead>
<Tbody>
{table.getRowModel().rows.map((row) => (
<Tr key={row.id}>
{row.getVisibleCells().map((cell) => (
<Td
key={cell.id}
isNumeric={
cell.column.columnDef.meta?.dataType === 'numeric'
}
borderBlock='1px solid gray'
fontFamily='mono'
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</Td>
))}
</Tr>
))}
</Tbody>
</Table>
</TableContainer>

<Flex align='center' justifyContent='space-between'>
<Flex align='center' gap='24px'>
<Flex>
<Button
variant='link'
onClick={() => table.setPageIndex(0)}
disabled={!table.getCanPreviousPage()}
>
{'<<'}
</Button>
<Button
variant='link'
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
{'<'}
</Button>
<Button
variant='link'
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
{'>'}
</Button>
<Button
variant='link'
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
disabled={!table.getCanNextPage()}
>
{'>>'}
</Button>
</Flex>
<Flex gap='12px'>
<Text>Page</Text>
<Text>
<strong>
{table.getState().pagination.pageIndex + 1} of{' '}
{table.getPageCount()}
</strong>
</Text>
</Flex>
<Box>|</Box>
<Flex align='center' gap='12px'>
<label htmlFor={`table-go-to-page-${id}`}>
<Text>Go to page:</Text>
</label>
<Input
id={`table-go-to-page-${id}`}
type='number'
defaultValue={table.getState().pagination.pageIndex + 1}
maxWidth='80px'
onChange={(e) => {
const page = e.target.value ? Number(e.target.value) - 1 : 0;
table.setPageIndex(page);
}}
/>
</Flex>
</Flex>
<Select
value={table.getState().pagination.pageSize}
onChange={(e) => {
table.setPageSize(Number(e.target.value));
}}
maxWidth='150px'
>
{[20, 50, 100, 200].map((pageSize) => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</Select>
</Flex>
</Box>
);
}
40 changes: 40 additions & 0 deletions apps/frontend/components/DebouncedInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useEffect, useState } from 'react';
import { Input, InputProps } from '@chakra-ui/react';

type DebouncedInputProps<T extends string | number> = {
value: T;
onChange: (value: T) => void;
debounce?: number;
} & Omit<InputProps, 'onChange'>;

// A debounced input react component
const DebouncedInput = <T extends string | number>({
value: initialValue,
onChange,
debounce = 500,
...props
}: DebouncedInputProps<T>) => {
const [value, setValue] = useState(initialValue);

useEffect(() => {
setValue(initialValue);
}, [initialValue]);

useEffect(() => {
const timeout = setTimeout(() => {
onChange(value);
}, debounce);

return () => clearTimeout(timeout);
}, [debounce, onChange, value]);

return (
<Input
{...props}
value={value}
onChange={(e) => setValue(e.target.value as T)}
/>
);
};

export default DebouncedInput;
Loading

0 comments on commit 28752f9

Please sign in to comment.