Skip to content

Commit

Permalink
Contributions Dashboard: Include children contributions (#10876)
Browse files Browse the repository at this point in the history
* refact: add title property to ComboSelectFilter items

* refact: include children accounts in IncomingContributions
  • Loading branch information
kewitz authored Dec 20, 2024
1 parent 83175f4 commit 75fa2e1
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 24 deletions.
7 changes: 5 additions & 2 deletions components/dashboard/filters/ComboSelectFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const SelectItem = ({
}: {
isSelected: boolean;
value: any;
label?: React.ReactNode;
label?: React.ReactNode | string;
onSelect: (value: any) => void;
valueRenderer?: ({
intl,
Expand All @@ -52,6 +52,7 @@ const SelectItem = ({
if (typeof label === 'string' && !keywords) {
keywords = [label];
}

return (
<CommandItem
onSelect={() => onSelect(value)}
Expand All @@ -69,7 +70,9 @@ const SelectItem = ({
<CheckIcon className={'h-4 w-4'} />
</div>

<div className="truncate">{label ?? String(value)}</div>
<div className="truncate" title={typeof label === 'string' ? label : undefined}>
{label ?? String(value)}
</div>
</CommandItem>
);
};
Expand Down
80 changes: 68 additions & 12 deletions components/dashboard/sections/contributions/Contributions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ const dashboardContributionsMetadataQuery = gql`
$onlyExpectedFunds: Boolean!
$expectedFundsFilter: ExpectedFundsFilter
$includeHostedAccounts: Boolean!
$includeChildrenAccounts: Boolean
) {
account(slug: $slug) {
id
Expand All @@ -85,6 +86,22 @@ const dashboardContributionsMetadataQuery = gql`
settings
imageUrl
currency
childrenAccounts {
totalCount
nodes {
id
slug
name
... on AccountWithContributions {
tiers {
nodes {
id
name
}
}
}
}
}
... on AccountWithContributions {
canStartResumeContributionsProcess
hasResumeContributionsProcessStarted
Expand Down Expand Up @@ -116,6 +133,7 @@ const dashboardContributionsMetadataQuery = gql`
filter: $filter
expectedFundsFilter: $expectedFundsFilter
includeHostedAccounts: $includeHostedAccounts
includeChildrenAccounts: $includeChildrenAccounts
) {
totalCount
}
Expand All @@ -141,6 +159,7 @@ const dashboardContributionsMetadataQuery = gql`
status: [ACTIVE, ERROR]
includeIncognito: true
includeHostedAccounts: $includeHostedAccounts
includeChildrenAccounts: $includeChildrenAccounts
) @skip(if: $onlyExpectedFunds) {
totalCount
}
Expand All @@ -160,6 +179,7 @@ const dashboardContributionsMetadataQuery = gql`
includeIncognito: true
minAmount: 1
includeHostedAccounts: $includeHostedAccounts
includeChildrenAccounts: $includeChildrenAccounts
) @skip(if: $onlyExpectedFunds) {
totalCount
}
Expand All @@ -169,6 +189,7 @@ const dashboardContributionsMetadataQuery = gql`
includeIncognito: true
expectedFundsFilter: $expectedFundsFilter
includeHostedAccounts: $includeHostedAccounts
includeChildrenAccounts: $includeChildrenAccounts
) {
totalCount
}
Expand Down Expand Up @@ -224,6 +245,7 @@ const dashboardContributionsQuery = gql`
$maxAmount: Int
$paymentMethod: PaymentMethodReferenceInput
$includeHostedAccounts: Boolean!
$includeChildrenAccounts: Boolean
$dateFrom: DateTime
$dateTo: DateTime
$expectedDateFrom: DateTime
Expand Down Expand Up @@ -252,6 +274,7 @@ const dashboardContributionsQuery = gql`
limit: $limit
paymentMethod: $paymentMethod
includeHostedAccounts: $includeHostedAccounts
includeChildrenAccounts: $includeChildrenAccounts
expectedFundsFilter: $expectedFundsFilter
orderBy: $orderBy
chargedDateFrom: $chargedDateFrom
Expand All @@ -269,50 +292,52 @@ const dashboardContributionsQuery = gql`
${managedOrderFragment}
`;

const getColumns = ({ intl, isIncoming, includeHostedAccounts, onlyExpectedFunds }) => {
const accounts = {
const getColumns = ({ intl, isIncoming, includeHostedAccounts, includeChildrenAccounts, onlyExpectedFunds }) => {
const accounts = swap => ({
accessorKey: 'toAccount',
header: intl.formatMessage({ defaultMessage: 'Collective & Contributors', id: 'kklCrk' }),
meta: { className: 'max-w-[400px] overflow-hidden' },
cell: ({ cell, row }) => {
const toAccount = cell.getValue();
const fromAccount = row.original.fromAccount;
const big = swap ? fromAccount : toAccount;
const small = swap ? toAccount : fromAccount;
return (
<div className="flex items-center gap-5">
<div className="relative">
<div>
<AccountHoverCard
account={toAccount}
account={big}
trigger={
<span>
<Avatar size={32} collective={toAccount} displayTitle={false} />
<Avatar size={32} collective={big} displayTitle={false} />
</span>
}
/>
</div>
<div className="absolute -bottom-[6px] -right-[6px] rounded-full">
<AccountHoverCard
account={fromAccount}
account={small}
trigger={
<span>
<Avatar size={16} collective={fromAccount} displayTitle={false} />
<Avatar size={16} collective={small} displayTitle={false} />
</span>
}
/>
</div>
</div>
<div className="overflow-hidden">
<div className="overflow-hidden text-ellipsis whitespace-nowrap text-sm leading-5">
{toAccount.name || toAccount.slug}
{big.name || big.slug}
</div>
<div className="overflow-hidden text-ellipsis whitespace-nowrap text-xs font-normal leading-4 text-slate-700">
{fromAccount.name || fromAccount.slug}
{small.name || small.slug}
</div>
</div>
</div>
);
},
};
});

const toAccount = {
accessorKey: 'toAccount',
Expand Down Expand Up @@ -487,7 +512,11 @@ const getColumns = ({ intl, isIncoming, includeHostedAccounts, onlyExpectedFunds

return compact([
onlyExpectedFunds ? contributionId : null,
includeHostedAccounts ? accounts : isIncoming ? fromAccount : toAccount,
includeHostedAccounts || includeChildrenAccounts
? accounts(isIncoming && includeChildrenAccounts)
: isIncoming
? fromAccount
: toAccount,
chargeDate,
amount,
frequency,
Expand All @@ -509,9 +538,16 @@ type ContributionsProps = DashboardSectionProps & {
direction?: 'INCOMING' | 'OUTGOING';
onlyExpectedFunds?: boolean;
includeHostedAccounts?: boolean;
includeChildrenAccounts?: boolean;
};

const Contributions = ({ accountSlug, direction, onlyExpectedFunds, includeHostedAccounts }: ContributionsProps) => {
const Contributions = ({
accountSlug,
direction,
onlyExpectedFunds,
includeHostedAccounts,
includeChildrenAccounts,
}: ContributionsProps) => {
const { toast } = useToast();

const [expireOrder] = useMutation(
Expand Down Expand Up @@ -559,6 +595,7 @@ const Contributions = ({ accountSlug, direction, onlyExpectedFunds, includeHoste
onlyExpectedFunds: !!onlyExpectedFunds,
expectedFundsFilter: onlyExpectedFunds ? ExpectedFundsFilter.ALL_EXPECTED_FUNDS : null,
includeHostedAccounts: !!includeHostedAccounts,
includeChildrenAccounts: !!includeChildrenAccounts,
},
context: API_V2_CONTEXT,
fetchPolicy: typeof window !== 'undefined' ? 'cache-and-network' : 'cache-first',
Expand Down Expand Up @@ -688,9 +725,26 @@ const Contributions = ({ accountSlug, direction, onlyExpectedFunds, includeHoste
: null,
].filter(Boolean);

const tierOptions = React.useMemo(() => {
if (!includeChildrenAccounts) {
return [];
}
if (metadata?.account.childrenAccounts.nodes?.length === 0) {
return metadata.account?.tiers?.nodes.map(tier => ({ label: tier.name, value: tier.id }));
} else {
const makeOption = account =>
account?.tiers?.nodes.map(tier => ({ label: `${tier.name} (${account.name})`, value: tier.id }));
const options = makeOption(metadata?.account);
metadata?.account.childrenAccounts.nodes.forEach(children => {
options.push(...makeOption(children));
});
return options;
}
}, [metadata?.account]);

const filterMeta: FilterMeta = {
currency: metadata?.account?.currency,
tiers: isIncoming ? metadata?.account?.tiers?.nodes : [],
tierOptions: isIncoming ? tierOptions : [],
};

const queryFilter = useQueryFilter({
Expand All @@ -716,6 +770,7 @@ const Contributions = ({ accountSlug, direction, onlyExpectedFunds, includeHoste
filter: direction || 'OUTGOING',
includeIncognito: true,
includeHostedAccounts: !!includeHostedAccounts,
includeChildrenAccounts: !!includeChildrenAccounts,
...queryFilter.variables,
...(onlyExpectedFunds
? {
Expand Down Expand Up @@ -759,6 +814,7 @@ const Contributions = ({ accountSlug, direction, onlyExpectedFunds, includeHoste
intl,
isIncoming,
includeHostedAccounts,
includeChildrenAccounts: includeChildrenAccounts && metadata?.account?.childrenAccounts?.nodes?.length > 0,
onlyExpectedFunds,
});
const currentViewCount = views.find(v => v.id === queryFilter.activeViewId)?.count;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { DashboardSectionProps } from '../../types';
import Contributions from './Contributions';

const IncomingContributions = (props: DashboardSectionProps) => {
return <Contributions {...props} direction="INCOMING" />;
return <Contributions {...props} direction="INCOMING" includeChildrenAccounts={true} />;
};

export default IncomingContributions;
14 changes: 5 additions & 9 deletions components/dashboard/sections/contributions/filters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ type FilterValues = z.infer<typeof schema>;

export type FilterMeta = {
currency?: Currency;
tiers?: Array<{ id: string; name: string }>;
tierOptions?: Array<{ label: string; value: string }>;
};

type GraphQLQueryVariables = DashboardRecurringContributionsQueryVariables;
Expand Down Expand Up @@ -103,14 +103,10 @@ export const filters: FilterComponentConfigs<FilterValues, FilterMeta> = {
},
tier: {
labelMsg: defineMessage({ defaultMessage: 'Tier', id: 'b07w+D' }),
Component: ({ meta, ...props }) => (
<ComboSelectFilter
options={meta.tiers?.map(({ id, name }) => ({ label: name, value: id })) ?? []}
isMulti
{...props}
/>
),
valueRenderer: ({ value, meta }) => meta.tiers?.find(tier => tier.id === value)?.name ?? value,
Component: ({ meta, ...props }) => {
return <ComboSelectFilter options={meta.tierOptions} isMulti {...props} />;
},
valueRenderer: ({ value, meta }) => meta.tierOptions?.find(tier => tier.value === value)?.label ?? value,
},
frequency: {
labelMsg: defineMessage({ id: 'Frequency', defaultMessage: 'Frequency' }),
Expand Down

0 comments on commit 75fa2e1

Please sign in to comment.