Skip to content

Commit

Permalink
feature(website): dynamic expenses stats on website (#826)
Browse files Browse the repository at this point in the history
  • Loading branch information
mkue authored May 4, 2024
1 parent 9d1e2ba commit cffd1a5
Show file tree
Hide file tree
Showing 9 changed files with 97 additions and 69 deletions.
14 changes: 9 additions & 5 deletions shared/locales/de/website-finances.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,16 @@
"payments-last-month": "Letzter Monat: {{ value, currency }} an {{ recipientsCount }} Empfänger:innen",
"payments-future": "Künftige Auszahlungen: {{ value, currency }}",
"project-costs": "{{ value, currency }} Projektführungskosten",
"transaction-costs": "Zustellgebühren: {{ value, currency }}",
"transaction-costs-tooltip": "Kosten, die anfallen beim Geldtransfer von dir zu uns.",
"delivery-costs": "Auslieferungskosten: {{ value, currency }}",
"delivery-costs-tooltip": "Kosten, die anfallen beim Geldtransfer von uns zu den Empfänger:innen.",
"donation-fees": "Zustellgebühren: {{ value, currency }}",
"donation-fees-tooltip": "Kosten, die anfallen beim Geldtransfer von dir zu uns.",
"delivery-fees": "Auslieferungskosten: {{ value, currency }}",
"delivery-fees-tooltip": "Kosten, die anfallen beim Geldtransfer von uns zu den Empfänger:innen.",
"exchange-rate-loss": "Währungsverluste: {{ value, currency }}",
"exchange-rate-loss-tooltip": "Die Währungsverluste entstehen hauptsächlich durch die Inflation in Sierra Leone.",
"account-fees": "Kontoführungsgebühren: {{ value, currency }}",
"account-fees-tooltip": "Kosten, die anfallen für die Kontoführung in Sierra Leone und der Schweiz.",
"administrative-costs": "Administrative Kosten: {{ value, currency }}",
"administrative-costs-tooltip": "Kosten für IT Lizenzen, Hosting der Webseite, etc.",
"administrative-costs-tooltip": "Kosten für IT Lizenzen, Hosting der Webseite, und andere Dienstleistungen.",
"fundraising-costs": "Fundraising & Marketing: {{ value, currency }}",
"fundraising-costs-tooltip": "Kosten, die für Werbung und Fundraising anfallen.",
"staff-costs": "Lokales Personal: {{ value, currency }}",
Expand Down
14 changes: 9 additions & 5 deletions shared/locales/en/website-finances.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,16 @@
"payments-last-month": "Last month: {{ value, currency }} to {{ recipientsCount }} recipients",
"payments-future": "To be paid out: {{ value, currency }}",
"project-costs": "{{ value, currency }} Project Management Costs",
"transaction-costs": "Transaction Costs: {{ value, currency }}",
"transaction-costs-tooltip": "Costs incurred during the transfer of money from you to us.",
"delivery-costs": "Delivery Costs: {{ value, currency }}",
"delivery-costs-tooltip": "Costs incurred during the transfer of money from us to the recipients.",
"donation-fees": "Transaction Costs: {{ value, currency }}",
"donation-fees-tooltip": "Costs incurred during the transfer of money from you to us.",
"delivery-fees": "Delivery Costs: {{ value, currency }}",
"delivery-fees-tooltip": "Costs incurred during the transfer of money from us to the recipients.",
"exchange-rate-loss": "Exchange Rate Loss: {{ value, currency }}",
"exchange-rate-loss-tooltip": "Exchange rate losses occur mainly due to the inflation in Sierra Leone.",
"account-fees": "Account Fees: {{ value, currency }}",
"account-fees-tooltip": "Fees for our bank accounts in Switzerland and Sierra Leone.",
"administrative-costs": "Administrative Costs: {{ value, currency }}",
"administrative-costs-tooltip": "Costs for IT licenses, website hosting, etc.",
"administrative-costs-tooltip": "Fees for IT licenses, website hosting, and other services.",
"fundraising-costs": "Fundraising & Marketing: {{ value, currency }}",
"fundraising-costs-tooltip": "Costs incurred for advertising and fundraising.",
"staff-costs": "Local Staff: {{ value, currency }}",
Expand Down
4 changes: 2 additions & 2 deletions shared/src/types/expense.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ export const EXPENSES_FIRESTORE_PATH = 'expenses';

export enum ExpenseType {
AccountFees = 'account_fees',
Administrative = 'Administrative',
Administrative = 'administrative',
DeliveryFees = 'delivery_fees',
DonationFees = 'donation_fees',
ExchangeRateLoss = 'exchange_rate_loss',
FundraisingAdvertising = 'FundraisingAdvertising',
FundraisingAdvertising = 'fundraising_advertising',
Staff = 'staff',
}

Expand Down
12 changes: 6 additions & 6 deletions shared/src/utils/stats/ExpensesStatsCalculator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type ExpenseStatsEntry = Expense & {

export type ExpenseStats = {
totalExpensesByYear: { [year: string]: number };
totalExpensesByType: { [type: string]: number };
totalExpensesByType: Record<ExpenseType, number>;
totalExpensesByYearAndType: { [year: string]: { [type in ExpenseType]: number } };
};

Expand All @@ -33,14 +33,14 @@ export class ExpensesStatsCalculator {
);
}

public totalExpensesBy(group: 'type' | 'year'): { [type in string]?: number } {
public totalExpensesBy(group: 'type' | 'year'): Record<string, number> {
return this.expenses
.groupBy(group)
.map((expenses, group) => ({ [group]: _.sumBy(expenses, 'amount') }))
.map((expenses, group) => ({ [group as ExpenseType]: _.sumBy(expenses, 'amount') }))
.reduce((a, b) => ({ ...a, ...b }), {});
}

public totalExpensesByYearAndType(): { [year: string]: { [type in ExpenseType]: number } } {
public totalExpensesByYearAndType(): Record<string, Record<ExpenseType, number>> {
return this.expenses
.groupBy('year')
.map((expenses, year) => ({
Expand All @@ -54,8 +54,8 @@ export class ExpensesStatsCalculator {

public allStats(): ExpenseStats {
return {
totalExpensesByYear: this.totalExpensesBy('year') as { [year: string]: number },
totalExpensesByType: this.totalExpensesBy('type') as { [type in ExpenseType]: number },
totalExpensesByYear: this.totalExpensesBy('year') as Record<string, number>,
totalExpensesByType: this.totalExpensesBy('type') as Record<ExpenseType, number>,
totalExpensesByYearAndType: this.totalExpensesByYearAndType(),
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,7 @@ export type SectionProps = {
params: DefaultParams & { currency: string };
contributionStats: ContributionStats;
paymentStats: PaymentStats;
expenseStats: ExpenseStats;
costs: {
transaction: number;
delivery: number;
administrative: number;
fundraising: number;
staff: number;
};
expensesStats: ExpenseStats;
};

export default async function Page({ params }: TransparencyPageProps) {
Expand All @@ -51,17 +44,7 @@ export default async function Page({ params }: TransparencyPageProps) {
return { contributionStats, expensesStats, paymentStats };
};
const currency = params.currency.toUpperCase() as WebsiteCurrency;
const { contributionStats, expensesStats: expenseStats, paymentStats } = await getStats(currency);
console.info(JSON.stringify(expenseStats, null, 2));

// TODO: Calculate these costs dynamically
const costs = {
transaction: 8800,
delivery: 5700, // "Total operative expenses"
administrative: 5600, // "Other project costs"
fundraising: 4500,
staff: 9600,
};
const { contributionStats, expensesStats, paymentStats } = await getStats(currency);

return (
<div className="flex flex-col space-y-16 py-8">
Expand All @@ -70,29 +53,25 @@ export default async function Page({ params }: TransparencyPageProps) {
params={params}
contributionStats={contributionStats}
paymentStats={paymentStats}
costs={costs}
expenseStats={expenseStats}
expensesStats={expensesStats}
/>
<Section2
params={params}
contributionStats={contributionStats}
paymentStats={paymentStats}
costs={costs}
expenseStats={expenseStats}
expensesStats={expensesStats}
/>
<Section3
params={params}
contributionStats={contributionStats}
paymentStats={paymentStats}
costs={costs}
expenseStats={expenseStats}
expensesStats={expensesStats}
/>
<Section4
params={params}
contributionStats={contributionStats}
paymentStats={paymentStats}
costs={costs}
expenseStats={expenseStats}
expensesStats={expensesStats}
/>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import _ from 'lodash';
import { InfoCard } from './info-card';
import { SectionProps } from './page';

export async function Section2({ params, contributionStats, paymentStats, costs }: SectionProps) {
export async function Section2({ params, contributionStats, expensesStats, paymentStats }: SectionProps) {
const translator = await Translator.getInstance({ language: params.lang, namespaces: ['website-finances'] });
const expensesProject = _.sum(Object.values(costs));
const expensesProject = _.sum(Object.values(expensesStats.totalExpensesByType));

return (
<div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
'use client';

import { ExpenseStats } from '@socialincome/shared/src/utils/stats/ExpensesStatsCalculator';
import { Button, Card, CardContent, Typography } from '@socialincome/ui';
import { Children, PropsWithChildren, useState } from 'react';

Expand All @@ -17,12 +16,10 @@ type CountryCardProps = {
country: string;
total: string;
};
expenseStats: ExpenseStats; // TODO: remove again
};

/* eslint-disable @next/next/no-img-element */
export function CountryCard({ country, translations, expenseStats }: CountryCardProps) {
console.log(expenseStats);
export function CountryCard({ country, translations }: CountryCardProps) {
return (
<li>
<Card>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Typography } from '@socialincome/ui';
import { SectionProps } from './page';
import { CountryCard, CountryCardList } from './section-3-cards';

export async function Section3({ params, contributionStats, expenseStats }: SectionProps) {
export async function Section3({ params, contributionStats }: SectionProps) {
const translator = await Translator.getInstance({
language: params.lang,
namespaces: ['countries', 'website-finances'],
Expand Down Expand Up @@ -40,7 +40,6 @@ export async function Section3({ params, contributionStats, expenseStats }: Sect
},
}),
}}
expenseStats={expenseStats}
/>
))}
</CountryCardList>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import { InfoCard } from './info-card';
import { SectionProps } from './page';
import { roundAmount } from './section-1';

export async function Section4({ params, paymentStats, contributionStats, costs }: SectionProps) {
export async function Section4({ params, expensesStats, paymentStats, contributionStats }: SectionProps) {
const translator = await Translator.getInstance({ language: params.lang, namespaces: ['website-finances'] });
const expensesTotal = _.sum(Object.values(costs)) + paymentStats.totalPaymentsAmount;
const expensesTotal = _.sum(Object.values(expensesStats.totalExpensesByType)) + paymentStats.totalPaymentsAmount;
const reservesTotal = contributionStats.totalContributionsAmount - expensesTotal;
const exchangeRateSLE = await getLatestExchangeRate(firestoreAdmin, 'SLE');

Expand Down Expand Up @@ -74,14 +74,17 @@ export async function Section4({ params, paymentStats, contributionStats, costs
<div className="flex-inline flex items-center">
<Typography as="div" weight="bold" size="lg">
{translator.t('section-4.project-costs', {
context: { value: roundAmount(_.sum(Object.values(costs))), currency: params.currency },
context: {
value: roundAmount(_.sum(Object.values(expensesStats.totalExpensesByType))),
currency: params.currency,
},
})}
</Typography>
</div>
<Typography as="div" className="flex-inline flex items-center">
{translator.t('section-4.transaction-costs', {
{translator.t('section-4.donation-fees', {
context: {
value: roundAmount(costs.transaction),
value: roundAmount(expensesStats.totalExpensesByType.donation_fees),
currency: params.currency,
},
})}
Expand All @@ -90,52 +93,62 @@ export async function Section4({ params, paymentStats, contributionStats, costs
<TooltipTrigger>
<InformationCircleIcon className="mx-2 h-5 w-5" />
</TooltipTrigger>
<TooltipContent>{translator.t('section-4.transaction-costs-tooltip')}</TooltipContent>
<TooltipContent>{translator.t('section-4.donation-fees-tooltip')}</TooltipContent>
</Tooltip>
</TooltipProvider>
</Typography>
<Typography as="div" className="flex-inline flex items-center">
{translator.t('section-4.delivery-costs', {
context: { value: roundAmount(costs.delivery), currency: params.currency },
{translator.t('section-4.delivery-fees', {
context: {
value: roundAmount(expensesStats.totalExpensesByType.delivery_fees),
currency: params.currency,
},
})}
<TooltipProvider delayDuration={100}>
<Tooltip>
<TooltipTrigger>
<InformationCircleIcon className="mx-2 h-5 w-5" />
</TooltipTrigger>
<TooltipContent>{translator.t('section-4.delivery-costs-tooltip')}</TooltipContent>
<TooltipContent>{translator.t('section-4.delivery-fees-tooltip')}</TooltipContent>
</Tooltip>
</TooltipProvider>
</Typography>
<Typography as="div" className="flex-inline flex items-center">
{translator.t('section-4.administrative-costs', {
context: { value: roundAmount(costs.administrative), currency: params.currency },
{translator.t('section-4.exchange-rate-loss', {
context: {
value: roundAmount(expensesStats.totalExpensesByType.exchange_rate_loss),
currency: params.currency,
},
})}
<TooltipProvider delayDuration={100}>
<Tooltip>
<TooltipTrigger>
<InformationCircleIcon className="mx-2 h-5 w-5" />
</TooltipTrigger>
<TooltipContent>{translator.t('section-4.administrative-costs-tooltip')}</TooltipContent>
<TooltipContent>{translator.t('section-4.exchange-rate-loss-tooltip')}</TooltipContent>
</Tooltip>
</TooltipProvider>
</Typography>
<Typography as="div" className="flex-inline flex items-center">
{translator.t('section-4.fundraising-costs', {
context: { value: roundAmount(costs.fundraising), currency: params.currency },
{translator.t('section-4.account-fees', {
context: {
value: roundAmount(expensesStats.totalExpensesByType.account_fees),
currency: params.currency,
},
})}
<TooltipProvider delayDuration={100}>
<Tooltip>
<TooltipTrigger>
<InformationCircleIcon className="mx-2 h-5 w-5" />
</TooltipTrigger>
<TooltipContent>{translator.t('section-4.fundraising-costs-tooltip')}</TooltipContent>
<TooltipContent>{translator.t('section-4.account-fees-tooltip')}</TooltipContent>
</Tooltip>
</TooltipProvider>
</Typography>

<Typography as="div" className="flex-inline flex items-center">
{translator.t('section-4.staff-costs', {
context: { value: roundAmount(costs.staff), currency: params.currency },
context: { value: roundAmount(expensesStats.totalExpensesByType.staff), currency: params.currency },
})}
<TooltipProvider delayDuration={100}>
<Tooltip>
Expand All @@ -146,6 +159,38 @@ export async function Section4({ params, paymentStats, contributionStats, costs
</Tooltip>
</TooltipProvider>
</Typography>
<Typography as="div" className="flex-inline flex items-center">
{translator.t('section-4.fundraising-costs', {
context: {
value: roundAmount(expensesStats.totalExpensesByType.fundraising_advertising),
currency: params.currency,
},
})}
<TooltipProvider delayDuration={100}>
<Tooltip>
<TooltipTrigger>
<InformationCircleIcon className="mx-2 h-5 w-5" />
</TooltipTrigger>
<TooltipContent>{translator.t('section-4.fundraising-costs-tooltip')}</TooltipContent>
</Tooltip>
</TooltipProvider>
</Typography>
<Typography as="div" className="flex-inline flex items-center">
{translator.t('section-4.administrative-costs', {
context: {
value: roundAmount(expensesStats.totalExpensesByType.administrative),
currency: params.currency,
},
})}
<TooltipProvider delayDuration={100}>
<Tooltip>
<TooltipTrigger>
<InformationCircleIcon className="mx-2 h-5 w-5" />
</TooltipTrigger>
<TooltipContent>{translator.t('section-4.administrative-costs-tooltip')}</TooltipContent>
</Tooltip>
</TooltipProvider>
</Typography>
</div>
}
/>
Expand Down

0 comments on commit cffd1a5

Please sign in to comment.