Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/add accounting page #25

Merged
merged 34 commits into from
Jan 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
44ec7eb
feat(golden-ledger): add remote graphql schemas
moconnell Dec 19, 2022
a10fb11
fix _meta collision with prefix
psparacino Dec 20, 2022
b78d02f
chore(hasura): add docker auto-setup
moconnell Dec 21, 2022
6758468
fix(hasura): reload metadata after seeding data
moconnell Dec 21, 2022
223b16f
chore(docker): update readme
moconnell Dec 21, 2022
edeacc8
Merge branch 'develop' into feature/golden-ledger
moconnell Dec 22, 2022
2f3af31
Add TRANSACTIONS_QUERY
ECWireless Dec 27, 2022
0733992
Update caniuse
ECWireless Dec 27, 2022
37d5ae1
Add useTransactions
ECWireless Dec 27, 2022
ce19d51
Add data formatter for useTransactions
ECWireless Dec 28, 2022
528ef1f
Add Accounting page
ECWireless Dec 28, 2022
869c782
feat(tx-table): initial add
moconnell Jan 6, 2023
efdd466
refactor(tx-table): remove chakra-ui/icons dep
moconnell Jan 6, 2023
b0de50d
fix(tx-table): fix column sorting
moconnell Jan 6, 2023
0358bc1
feat(tx-table): add 'days held' column
moconnell Jan 6, 2023
93297bf
chore: Merge branch 'develop' into feature/add-accounting-page
moconnell Jan 6, 2023
c4a705f
fix(SiteLayout.tsx): show {error.message}
moconnell Jan 9, 2023
d638765
fix(accounting): query hasura
moconnell Jan 9, 2023
ca6fbcc
Minor: add useBalances hook (#32)
ECWireless Jan 9, 2023
d66a968
chore(hasura-setup): cut setup timeout to 1m
moconnell Jan 10, 2023
c1c05c0
feat(accounting): add balances table
moconnell Jan 10, 2023
d191525
treasury_token_history table added (#33)
ECWireless Jan 11, 2023
9bf099a
Add useTokenPrices hook
ECWireless Jan 11, 2023
f4a3e30
txnID and symbol columns added (#34)
psparacino Jan 11, 2023
eee4791
Minor: add price data to transactions table (#35)
ECWireless Jan 11, 2023
3edd532
Add tabs and cell borders to accounting page (#36)
ECWireless Jan 12, 2023
a56a3cf
styling(accounting): style tab title
moconnell Jan 12, 2023
a8d36cf
accounting page: add table filters (#37)
moconnell Jan 17, 2023
222a1b1
Add pagination for DataTable (#40)
ECWireless Jan 18, 2023
28cfbfa
Accounting: link DAOhaus member profile from transactions table (#39)
moconnell Jan 19, 2023
ddbc4b8
Minor: improve accounting export data (#41)
ECWireless Jan 19, 2023
df8281d
Merge branch 'develop' into feature/add-accounting-page
moconnell Jan 20, 2023
c2bd8ea
Feature/add accounting page add current prices table (#42)
psparacino Jan 21, 2023
d4c2230
Improve accounting page loader (#43)
ECWireless Jan 21, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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