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

Upgrade accounting page for moloch v3 (#149) #150

Merged
merged 1 commit into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions apps/frontend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,6 @@ NEXT_PUBLIC_WC_PROJECT_ID=
# https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens
GITHUB_API_TOKEN=
GITHUB_API_URL='https://api.github.com/graphql'

# create one for prod here: https://thegraph.com/studio/
NEXT_PUBLIC_THE_GRAPH_API_KEY=
11 changes: 8 additions & 3 deletions apps/frontend/components/BalancesTable.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { Link, Tooltip } from '@raidguild/design-system';
import { ITokenBalanceLineItem } from '@raidguild/dm-types';
import {
ITokenBalanceLineItem,
ITokenBalanceLineItemV3,
} from '@raidguild/dm-types';
import { minMaxNumberFilter, sortNumeric } from '@raidguild/dm-utils';
// @ts-expect-error - no types from RT
import { createColumnHelper } from '@tanstack/react-table';
Expand All @@ -8,10 +11,12 @@ import DataTable from './DataTable';
import TokenWithUsdValue from './TokenWithUsdValue';

interface BalancesTableProps {
data: ITokenBalanceLineItem[];
data: ITokenBalanceLineItem[] | ITokenBalanceLineItemV3[];
}

const columnHelper = createColumnHelper<ITokenBalanceLineItem>();
const columnHelper = createColumnHelper<
ITokenBalanceLineItem | ITokenBalanceLineItemV3
>();

const columns = [
columnHelper.accessor('tokenExplorerLink', {
Expand Down
7 changes: 5 additions & 2 deletions apps/frontend/components/SiteLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ interface SiteLayoutProps {
data?: any;
subheader?: ReactNode;
emptyDataPhrase?: string;
error?: Error;
error?: Error | boolean;
isLoading?: boolean;
minHeight?: string;
}
Expand Down Expand Up @@ -157,7 +157,10 @@ const SiteLayout = ({
minHeight={minHeight}
>
<Flex w='100%' justify='center' pt={40}>
<Heading size='md'>Error loading data: {error.message}</Heading>
<Heading size='md'>
Error loading data
{typeof error === 'object' && `: ${error.message}`}
</Heading>
</Flex>
</GeneralLayout>
);
Expand Down
6 changes: 3 additions & 3 deletions apps/frontend/components/TransactionsTable.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Link, Tooltip } from '@raidguild/design-system';
import { IVaultTransaction } from '@raidguild/dm-types';
import { IVaultTransaction, IVaultTransactionV2 } from '@raidguild/dm-types';
import {
formatNumber,
minMaxDateFilter,
Expand All @@ -14,14 +14,14 @@ import DataTable from './DataTable';
import TokenWithUsdValue from './TokenWithUsdValue';

interface TransactionsTableProps {
data: IVaultTransaction[];
data: IVaultTransaction[] | IVaultTransactionV2[];
}

const columnHelper = createColumnHelper<any>();

const columns = [
columnHelper.accessor('date', {
cell: (info) => info.getValue().toLocaleString(),
cell: (info) => info.getValue()?.toLocaleString(),
header: 'Date',
meta: {
dataType: 'datetime',
Expand Down
184 changes: 63 additions & 121 deletions apps/frontend/pages/accounting.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* eslint-disable no-useless-computed-key */
/* eslint-disable simple-import-sort/imports */
import {
Button,
Flex,
Expand All @@ -9,156 +9,97 @@ import {
TabPanels,
Tabs,
} from '@raidguild/design-system';
import { useAccounting, useMemberList } from '@raidguild/dm-hooks';
import {
IMember,
ITokenBalanceLineItem,
IVaultTransaction,
} from '@raidguild/dm-types';
import {
exportToCsv,
formatDate,
REGEX_ETH_ADDRESS,
} from '@raidguild/dm-utils';
import _ from 'lodash';
useMemberList,
useFormattedAccountingV3,
useAccountingV3,
} from '@raidguild/dm-hooks';
import { exportToCsv } from '@raidguild/dm-utils';
import { useSession } from 'next-auth/react';
import { NextSeo } from 'next-seo';
import Papa from 'papaparse';
import { useCallback, useMemo } from 'react';
import { useCallback } from 'react';

import _ from 'lodash';
import BalancesTable from '../components/BalancesTable';
import SiteLayout from '../components/SiteLayout';
import SpoilsTable from '../components/SpoilsTable';
import TransactionsTable from '../components/TransactionsTable';

export const Accounting = () => {
const { data: session } = useSession();

const token = _.get(session, 'token');
const { data, loading, error } = useAccounting({
token,
});

const { data: memberData } = useMemberList({
token,
limit: 1000,
});

const { balances, spoils, transactions, tokenPrices } = data;

const members = useMemo(() => {
const memberArray = _.flatten(
_.get(memberData, 'pages')
) as unknown as IMember[];
return _.keyBy(memberArray, (m: IMember) => m.ethAddress?.toLowerCase());
}, [memberData]);

const withPrices = useCallback(
<T extends ITokenBalanceLineItem | IVaultTransaction>(items: T[]) =>
items.map((t) => {
const formattedDate = formatDate(t.date);
const tokenSymbol = t.tokenSymbol?.toLowerCase();
if (
tokenPrices[tokenSymbol] &&
tokenPrices[tokenSymbol][formattedDate]
) {
return {
...t,
priceConversion: tokenPrices[tokenSymbol][formattedDate],
};
}
if (tokenSymbol.includes('xdai')) {
return {
...t,
priceConversion: 1,
};
}
return t;
}),
[tokenPrices]
);

const balancesWithPrices = useMemo(
() => withPrices(balances),
[balances, withPrices]
);
const { loading, isError, error } = useAccountingV3();

const transactionsWithPrices = useMemo(
() => withPrices(transactions),
[transactions, withPrices]
);

const transactionsWithPricesAndMembers = useMemo(
() =>
transactionsWithPrices.map((t) => {
const ethAddress = t.proposalApplicant.toLowerCase();
const m = members[ethAddress];
const memberLink = m?.ethAddress.match(REGEX_ETH_ADDRESS)
? `/members/${ethAddress}`
: undefined;

return {
...t,
memberLink,
memberName: m?.name,
memberEnsName: m?.ensName,
};
}),
[transactionsWithPrices, members]
);
const {
formattedSpoils: formattedSpoilsV3,
members,
balancesWithPrices: balancesWithPricesV3,
transactionsWithPrices: transactionsWithPricesV3,
transactionsWithPricesAndMembers: transactionsWithPricesAndMembersV3,
} = useFormattedAccountingV3(memberData);

const onExportCsv = useCallback(
(type: 'transactions' | 'balances' | 'spoils') => {
let csvString = '';
if (type === 'transactions') {
const formattedTransactions = transactionsWithPrices.map((t) => ({
['Date']: t.date,
['Tx Explorer Link']: t.txExplorerLink,
['Elapsed Days']: t.elapsedDays,
['Type']: t.type,
['Applicant']: t.proposalApplicant,
['Applicant Member']:
const formattedTransactions = transactionsWithPricesV3.map((t) => ({
Date: t.date,
'Tx Explorer Link': t.txExplorerLink,
'Elapsed Days': t.elapsedDays,
Type: t.type,
Applicant: t.proposalApplicant,
'Applicant Member':
members[t.proposalApplicant.toLowerCase()]?.name || '-',
['Shares']: t.proposalShares,
['Loot']: t.proposalLoot,
['Title']: t.proposalTitle,
['Counterparty']: t.counterparty,
['Counterparty Member']:
Shares: t.proposalShares,
Loot: t.proposalLoot,
Title: t.proposalTitle,
Counterparty: t.counterparty,
'Counterparty Member':
members[t.counterparty.toLowerCase()]?.name || '-',
['Token Symbol']: t.tokenSymbol,
['Token Decimals']: t.tokenDecimals,
['Token Address']: t.tokenAddress,
['Inflow']: t.in,
['Inflow USD']: t.priceConversion
'Token Symbol': t.tokenSymbol,
'Token Decimals': t.tokenDecimals,
'Token Address': t.tokenAddress,
Inflow: t.in,
'Inflow USD': t.priceConversion
? `$${(t.in * t.priceConversion).toLocaleString()}`
: '$-',
['Outflow']: t.out,
['Outflow USD']: t.priceConversion
Outflow: t.out,
'Outflow USD': t.priceConversion
? `$${(t.out * t.priceConversion).toLocaleString()}`
: '$-',
['Balance']: t.balance,
['Balance USD']: t.priceConversion
Balance: t.balance,
'Balance USD': t.priceConversion
? `$${(t.balance * t.priceConversion).toLocaleString()}`
: '$-',
}));
csvString = Papa.unparse(formattedTransactions);
} else if (type === 'balances') {
if (type === 'balances') {
const formattedBalances = balancesWithPrices.map((b) => ({
['Token']: b.tokenSymbol,
['Tx Explorer Link']: b.tokenExplorerLink,
['Inflow']: b.inflow.tokenValue,
['Inflow USD']: b.priceConversion
const formattedBalances = balancesWithPricesV3.map((b) => ({
Token: b.tokenSymbol,
'Tx Explorer Link': b.tokenExplorerLink,
Inflow: b.inflow.tokenValue,
'Inflow USD': b.priceConversion
? `$${(
Number(b.inflow.tokenValue) * b.priceConversion
).toLocaleString()}`
: '$-',
['Outflow']: b.outflow.tokenValue,
['Outflow USD']: b.priceConversion
Outflow: b.outflow.tokenValue,
'Outflow USD': b.priceConversion
? `$${(
Number(b.outflow.tokenValue) * b.priceConversion
).toLocaleString()}`
: '$-',
['Balance']: b.closing.tokenValue,
['Balance USD']: b.priceConversion
Balance: b.closing.tokenValue,
'Balance USD': b.priceConversion
? `$${(
Number(b.closing.tokenValue) * b.priceConversion
).toLocaleString()}`
Expand All @@ -167,19 +108,19 @@ export const Accounting = () => {
csvString = Papa.unparse(formattedBalances);
}
} else if (type === 'spoils') {
const formattedSpoils = spoils.map((s) => ({
['Date']: s.date,
['Raid']: s.raidName,
const formattedSpoils = formattedSpoilsV3.map((s) => ({
Date: s.date,
Raid: s.raidName,
// TODO: Get this dynamically from the subgraph
['Token Symbol']: 'wxDAI',
['To DAO Treasury']: `$${s.parentShare.toLocaleString()}`,
['To Raid Party']: `$${s.childShare.toLocaleString()}`,
'Token Symbol': 'wxDAI',
'To DAO Treasury': `$${s.parentShare.toLocaleString()}`,
'To Raid Party': `$${s.childShare.toLocaleString()}`,
}));
csvString = Papa.unparse(formattedSpoils);
}
exportToCsv(csvString, `raidguild-treasury-${type}`);
},
[balancesWithPrices, members, spoils, transactionsWithPrices]
[balancesWithPricesV3, members, formattedSpoilsV3, transactionsWithPricesV3]
);

return (
Expand All @@ -189,13 +130,14 @@ export const Accounting = () => {
<SiteLayout
isLoading={loading}
data={[
...transactionsWithPricesAndMembers,
...balances,
...Object.values(tokenPrices),
...transactionsWithPricesV3,
...transactionsWithPricesAndMembersV3,
...balancesWithPricesV3,
// ...Object.values(tokenPrices),
]}
subheader={<Heading>Accounting</Heading>}
emptyDataPhrase='No transactions'
error={error}
error={error && isError}
>
<Tabs align='center' colorScheme='whiteAlpha' variant='soft-rounded'>
<TabList>
Expand Down Expand Up @@ -225,7 +167,7 @@ export const Accounting = () => {
Export Balances
</Button>
</Flex>
<BalancesTable data={balancesWithPrices} />
<BalancesTable data={balancesWithPricesV3} />
</TabPanel>
<TabPanel>
<Flex
Expand All @@ -241,7 +183,7 @@ export const Accounting = () => {
Export Transactions
</Button>
</Flex>
<TransactionsTable data={transactionsWithPricesAndMembers} />
<TransactionsTable data={transactionsWithPricesV3} />
</TabPanel>
<TabPanel>
<Flex
Expand All @@ -257,7 +199,7 @@ export const Accounting = () => {
Export Spoils
</Button>
</Flex>
<SpoilsTable data={spoils} />
<SpoilsTable data={formattedSpoilsV3} />
</TabPanel>
</TabPanels>
</Tabs>
Expand Down
14 changes: 14 additions & 0 deletions libs/dm-graphql/src/queries/accounting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,17 @@ export const TRANSACTIONS_QUERY = gql`
}
}
`;

export const TRANSACTIONS_QUERY_V3 = gql`
query AccountingQuery {
raids(
where: { invoice_address: { _is_null: false } }
order_by: { created_at: desc }
) {
id
invoice_address
name
created_at
}
}
`;
5 changes: 4 additions & 1 deletion libs/dm-hooks/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { default as useAccounting } from './useAccounting';
export { default as useAccountingV2 } from './useAccountingV2';
export { default as useAccountingV3 } from './useAccountingV3';
export { default as useApplicationDetail } from './useApplicationDetail';
export {
default as useApplicationList,
Expand All @@ -15,6 +16,8 @@ export { useContacts } from './useContacts';
export { default as useContactUpdate } from './useContactUpdate';
export { default as useDashboardList } from './useDashboardList';
export { default as useDefaultTitle } from './useDefaultTitle';
export { default as useFormattedAccountingV2 } from './useFormattedAccountingV2';
export { default as useFormattedAccountingV3 } from './useFormattedAccountingV3';
export * from './useLinks';
export { default as useLinksUpdate } from './useLinksUpdate';
export { default as useMemberCreate } from './useMemberCreate';
Expand Down
Loading