@withpotter/pages-admin 0.13.0-next.123 → 0.13.0-next.124
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from "react";
|
|
4
4
|
import Link from "next/link";
|
|
5
|
-
import { EmptyNote } from "@withpotter/ui-kit";
|
|
6
5
|
import { TimeSeriesChart } from "@withpotter/components/charts";
|
|
7
6
|
import {
|
|
8
7
|
earningsFigures,
|
|
@@ -40,6 +39,21 @@ const WHAT = {
|
|
|
40
39
|
payments: "Card payment fees that are not tagged as online or POS.",
|
|
41
40
|
other: "Fees from anything not listed here."
|
|
42
41
|
};
|
|
42
|
+
const NOTHING_YET = (currency) => ({
|
|
43
|
+
currency,
|
|
44
|
+
exponent: 2,
|
|
45
|
+
feeMinor: "0",
|
|
46
|
+
providerMinor: "0",
|
|
47
|
+
netMinor: "0",
|
|
48
|
+
previousFeeMinor: "0",
|
|
49
|
+
previousProviderMinor: "0",
|
|
50
|
+
count: 0,
|
|
51
|
+
providerCostsRecorded: false,
|
|
52
|
+
bySource: [],
|
|
53
|
+
series: [],
|
|
54
|
+
previousSeries: [],
|
|
55
|
+
topMerchants: []
|
|
56
|
+
});
|
|
43
57
|
const MINOR = /* @__PURE__ */ new Set(["transfers", "fx", "lending", "other"]);
|
|
44
58
|
function named(parts) {
|
|
45
59
|
const rest = parts.filter((p) => MINOR.has(p.source));
|
|
@@ -96,9 +110,7 @@ function EarningsSummary({
|
|
|
96
110
|
);
|
|
97
111
|
if (isLoading || !data) return /* @__PURE__ */ jsx(AdmLoading, { label: "Loading what Olive earns\u2026" });
|
|
98
112
|
const wanted = currency || recurring.currency || "NGN";
|
|
99
|
-
const cur = data.currencies.find((c) => c.currency === wanted) ?? data.currencies[0];
|
|
100
|
-
if (!cur)
|
|
101
|
-
return /* @__PURE__ */ jsx(EmptyNote, { className: styles.empty, children: "No fees have been earned in this period yet." });
|
|
113
|
+
const cur = data.currencies.find((c) => c.currency === wanted) ?? data.currencies[0] ?? NOTHING_YET(wanted);
|
|
102
114
|
const plan = recurring.currency === cur.currency ? recurring.collected ?? null : null;
|
|
103
115
|
const f = earningsFigures(cur, plan);
|
|
104
116
|
const have = named(f.parts);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/admin/revenue/earnings/earnings-summary.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport Link from 'next/link';\nimport { EmptyNote } from '@withpotter/ui-kit';\nimport { TimeSeriesChart } from '@withpotter/components/charts';\nimport type { PlatformRevenue } from '@withpotter/lib/admin-api';\nimport {\n earningsFigures,\n pctChange,\n toMajor,\n type EarningsPart,\n} from '@withpotter/lib/admin-earnings-model';\nimport { LoadFailed } from '../../audit/load-failed';\nimport { AdmLoading, Panel, StatCard, moneyIn, toTrend } from '../../_ui';\nimport { useSourceColors } from './earnings-colors';\nimport { CompareBars, PieWithDetails } from './earnings-legend';\nimport { useEarnings } from './use-earnings';\nimport styles from '../revenue.module.css';\nimport ledger from '../ledger.module.css';\nimport earn from './earnings.module.css';\n\nconst integer = new Intl.NumberFormat('en-US');\n\n/** The streams as the owner names them, in the order they are read. */\nconst ORDER = ['online', 'pos', 'payouts', 'settlements', 'cash', 'plans', 'payments', 'other'] as const;\n\n/** Streams that always have a card, even at zero; the rest appear when they earned something. */\nconst ALWAYS = new Set(['online', 'pos', 'payouts', 'settlements', 'cash', 'plans']);\n\nconst TITLES: Record<string, string> = {\n online: 'Online card payments',\n pos: 'POS card payments and transfers',\n payouts: 'Payouts',\n settlements: \"Settlements to merchants' banks\",\n cash: 'Cash received',\n plans: 'Plan subscriptions',\n payments: 'Other card payments',\n other: 'Other fees',\n};\n\nconst WHAT: Record<string, string> = {\n online: 'What Olive keeps when a customer pays a merchant by card online, including money that arrives through the payment gateway.',\n pos: 'What Olive keeps when a customer pays at a merchant\\'s POS terminal, by card or by transfer.',\n payouts: 'What Olive charges when a merchant sends money out of their wallet.',\n settlements: 'What Olive charges when a merchant\\'s money is settled to their bank account.',\n cash: 'What Olive charges when cash is received for a merchant.',\n plans: 'What merchants pay Olive for their plan, however they paid.',\n payments: 'Card payment fees that are not tagged as online or POS.',\n other: 'Fees from anything not listed here.',\n};\n\ntype Metric = 'earned' | 'costs' | 'profit';\n\nconst MINOR = new Set(['transfers', 'fx', 'lending', 'other']);\n\n/** Anything outside the named streams is one \"Other fees\" part, so the parts still add up to the total. */\nfunction named(parts: EarningsPart[]): EarningsPart[] {\n const rest = parts.filter((p) => MINOR.has(p.source));\n const main = parts.filter((p) => !MINOR.has(p.source));\n const sum = (k: 'value' | 'previous' | 'count') => rest.reduce((a, p) => a + p[k], 0);\n\n if (sum('value') <= 0 && sum('previous') <= 0) return main;\n\n return [\n ...main,\n { source: 'other', label: 'Other fees', value: sum('value'), previous: sum('previous'), count: sum('count') },\n ];\n}\n\n/** A card that picks which measure the chart shows, like the metric tabs of an analytics page. */\nfunction MetricCard({\n on,\n onPick,\n span,\n children,\n}: {\n on: boolean;\n onPick: () => void;\n span: string;\n children: React.ReactNode;\n}) {\n return (\n <div\n className={`${span} ${earn.metric} ${on ? earn.metricOn : ''}`}\n role=\"button\"\n tabIndex={0}\n aria-pressed={on}\n onClick={onPick}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onPick();\n }\n }}\n >\n {children}\n </div>\n );\n}\n\nconst count = (n: number) => `${integer.format(n)} ${n === 1 ? 'transaction' : 'transactions'}`;\n\n/**\n * What Olive earns, as the owner reads it: the total, what it cost, what is left, each stream on its own card, where it\n * comes from, how it moves and which merchants it comes from. One currency at a time, never a mix.\n */\nexport function EarningsSummary({\n startDate,\n endDate,\n currency,\n recurring,\n span,\n}: {\n startDate?: string;\n endDate?: string;\n /** The currency filter's choice; empty follows the billing currency. */\n currency: string;\n recurring: PlatformRevenue['recurring'];\n span: string;\n}) {\n const { data, isLoading, isError, refetch } = useEarnings({ startDate, endDate });\n const colors = useSourceColors();\n const [metric, setMetric] = useState<Metric>('earned');\n\n if (isError)\n return (\n <LoadFailed\n message=\"Earnings could not be loaded. This box may not have them yet.\"\n onRetry={() => void refetch()}\n />\n );\n\n if (isLoading || !data) return <AdmLoading label=\"Loading what Olive earns…\" />;\n\n const wanted = currency || recurring.currency || 'NGN';\n const cur = data.currencies.find((c) => c.currency === wanted) ?? data.currencies[0];\n\n if (!cur)\n return (\n <EmptyNote className={styles.empty}>No fees have been earned in this period yet.</EmptyNote>\n );\n\n const plan = recurring.currency === cur.currency ? (recurring.collected ?? null) : null;\n const f = earningsFigures(cur, plan);\n const have = named(f.parts);\n const parts = ORDER.filter((k) => ALWAYS.has(k) || have.some((p) => p.source === k && (p.value !== 0 || p.previous !== 0)))\n .map(\n (k): EarningsPart =>\n have.find((p) => p.source === k) ?? { source: k, label: TITLES[k], value: 0, previous: 0, count: 0 },\n )\n .sort((a, b) => ORDER.indexOf(a.source as never) - ORDER.indexOf(b.source as never));\n const money = moneyIn(cur.currency, 2);\n const caption = `${span[0].toUpperCase()}${span.slice(1)} · ${cur.currency}`;\n const margin = f.gross > 0 ? Math.round((f.net / f.gross) * 1000) / 10 : null;\n const green = colors.series[2];\n const red = colors.series[7];\n const byMethod = (recurring.collectedByMethod ?? []).filter((m) => m.amount > 0);\n const day = (p: { feeMinor: string; providerMinor: string }) => {\n const fee = toMajor(p.feeMinor, cur.exponent);\n const cost = toMajor(p.providerMinor, cur.exponent);\n\n return metric === 'earned' ? fee : metric === 'costs' ? cost : fee - cost;\n };\n const trend = toTrend(cur.series.map((p) => ({ date: p.date, value: day(p) })), 'value');\n const before = toTrend(cur.previousSeries.map((p) => ({ date: p.date, value: day(p) })), 'value');\n const feeSpark = cur.series.map((p) => toMajor(p.feeMinor, cur.exponent));\n const chartTitle = { earned: 'Fees earned each day', costs: 'Processing costs each day', profit: 'Profit each day' }[metric];\n const topTotal = cur.topMerchants.reduce((a, m) => a + toMajor(m.feeMinor, cur.exponent), 0);\n const label = (p: EarningsPart) => TITLES[p.source] ?? p.label;\n\n return (\n <>\n <p className={ledger.sectionNote}>\n How money flows through Olive: what it earned, what it cost, what is left, and where it came from. Showing{' '}\n {cur.currency}\n {data.currencies.length > 1 ? '; use the currency filter for the others.' : '.'}\n </p>\n\n <section aria-label=\"Earned, costs and profit\" className=\"adm-grid\">\n <MetricCard on={metric === 'earned'} onPick={() => setMetric('earned')} span=\"adm-span-3\">\n <StatCard\n label=\"Total earned\"\n accent={colors.series[0]}\n spark={feeSpark}\n sparkColor={colors.series[0]}\n value={money(f.gross)}\n sub=\"Fees plus plan income\"\n delta={{ pct: pctChange(f.gross, f.feesBefore + f.plan), caption: 'vs the period before' }}\n hint=\"Everything Olive earned in this period: fees on card payments, payouts, settlements and cash, plus what merchants paid for their plans.\"\n />\n </MetricCard>\n <MetricCard on={metric === 'costs'} onPick={() => setMetric('costs')} span=\"adm-span-3\">\n <StatCard\n label=\"Processing costs\"\n accent={red}\n value={money(cur.providerCostsRecorded ? f.provider : 0)}\n sub={cur.providerCostsRecorded ? 'Charged by the processors' : 'None recorded yet'}\n hint=\"What the payment processors kept from the same payments, as far as the system records it. Until costs are recorded this stays at zero and profit equals what was earned.\"\n />\n </MetricCard>\n <MetricCard on={metric === 'profit'} onPick={() => setMetric('profit')} span=\"adm-span-3\">\n <StatCard\n label=\"Profit\"\n accent={green}\n value={money(f.net)}\n sub={cur.providerCostsRecorded ? 'After processing costs' : 'Same as earned until costs are recorded'}\n hint=\"Total earned less the processing costs the system records. Running costs such as hosting and staff are not included.\"\n />\n </MetricCard>\n <div className=\"adm-span-3\">\n <StatCard\n label=\"Margin\"\n accent={colors.series[3]}\n value={margin === null ? '–' : `${margin}%`}\n sub=\"Profit as a share of earned\"\n hint=\"Profit divided by total earned.\"\n />\n </div>\n </section>\n\n <div className=\"adm-grid\">\n <div className=\"adm-span-12\">\n <Panel title={chartTitle} caption={`${caption} · dotted line is the period before`} height=\"calc(var(--adm-panel-h) + 32px)\">\n <div className={styles.flatChart}>\n <TimeSeriesChart\n data={trend}\n color={metric === 'costs' ? red : metric === 'profit' ? green : colors.series[0]}\n formatValue={money}\n compare={before}\n compareLabel=\"Period before\"\n emptyMessage=\"Nothing was recorded on any day in this period\"\n height={240}\n />\n </div>\n </Panel>\n </div>\n </div>\n\n <h2 className={ledger.sectionTitle}>Where the money comes from</h2>\n <section aria-label=\"Earnings by stream\" className=\"adm-grid\">\n {parts.map((p) => (\n <div key={p.source} className=\"adm-span-4\">\n <StatCard\n label={label(p)}\n accent={colors[p.source]}\n value={money(p.value)}\n sub={p.source === 'plans' ? 'Collected in this period' : p.count === 0 ? 'Nothing in this period' : count(p.count)}\n hint={WHAT[p.source]}\n delta={\n p.source === 'plans'\n ? undefined\n : { pct: pctChange(p.value, p.previous), caption: 'vs the period before' }\n }\n />\n </div>\n ))}\n <div className=\"adm-span-4\">\n <StatCard\n label=\"Olive account and outlets\"\n accent=\"var(--text-tertiary)\"\n value=\"Not counted yet\"\n sub=\"No earnings recorded\"\n hint=\"Money received through the Olive account and outlets. The bank system does not report a fee for it yet, so nothing can be added here.\"\n />\n </div>\n </section>\n\n <div className=\"adm-grid\">\n <div className=\"adm-span-4\">\n <Panel title=\"Where it comes from\" caption={caption} height=\"auto\">\n {parts.some((p) => p.value > 0) ? (\n <PieWithDetails\n money={money}\n centerLabel=\"Earned\"\n slices={parts.map((p) => ({\n key: p.source,\n label: label(p),\n value: p.value,\n color: colors[p.source],\n detail: p.source === 'plans' ? 'Collected' : count(p.count),\n }))}\n />\n ) : (\n <p className={styles.empty}>Nothing earned in this period.</p>\n )}\n </Panel>\n </div>\n <div className=\"adm-span-4\">\n <Panel title=\"What Olive keeps\" caption={caption} height=\"auto\">\n {cur.providerCostsRecorded && f.gross > 0 ? (\n <PieWithDetails\n money={money}\n centerLabel=\"Earned\"\n slices={[\n { key: 'profit', label: 'Profit', value: Math.max(0, f.net), color: green, detail: 'After processing costs' },\n { key: 'cost', label: 'Processing costs', value: Math.max(0, f.provider), color: red, detail: 'Charged by the processors' },\n ]}\n />\n ) : (\n <p className={styles.empty}>\n No processing cost is recorded for these fees yet, so profit equals what was earned.\n </p>\n )}\n </Panel>\n </div>\n <div className=\"adm-span-4\">\n <Panel title=\"How plans were paid\" caption={caption} height=\"auto\">\n {byMethod.length > 0 && recurring.currency === cur.currency ? (\n <PieWithDetails\n money={money}\n centerLabel=\"Plans\"\n slices={byMethod.map((m, i) => ({\n key: m.method,\n label: m.method,\n value: m.amount,\n color: colors.series[i % colors.series.length],\n detail: `${integer.format(m.payments)} ${m.payments === 1 ? 'payment' : 'payments'}`,\n }))}\n />\n ) : (\n <p className={styles.empty}>No plan payments in this period.</p>\n )}\n </Panel>\n </div>\n </div>\n\n <div className=\"adm-grid\">\n <div className=\"adm-span-6\">\n <Panel title=\"Stream by stream, against the period before\" caption={caption} height=\"auto\">\n <CompareBars\n money={money}\n rows={parts\n .filter((p) => p.source !== 'plans')\n .map((p) => ({\n key: p.source,\n label: label(p),\n color: colors[p.source],\n value: p.value,\n previous: p.previous,\n }))}\n />\n </Panel>\n </div>\n <div className=\"adm-span-6\">\n <Panel title=\"The merchants it comes from\" caption={`Fees, ${caption}`} height=\"auto\">\n {cur.topMerchants.length === 0 ? (\n <p className={styles.empty}>No fees to show in this period.</p>\n ) : (\n <ol className={earn.rankList}>\n {cur.topMerchants.map((m, i) => {\n const value = toMajor(m.feeMinor, cur.exponent);\n const widest = toMajor(cur.topMerchants[0].feeMinor, cur.exponent);\n\n return (\n <li key={`${m.tenantId ?? 'none'}-${i}`} className={earn.rankRow}>\n <span className={earn.rankName}>\n {m.tenantId && m.name ? (\n <Link href={`/admin/tenants/${m.tenantId}`}>{m.name}</Link>\n ) : (\n 'No merchant recorded'\n )}\n </span>\n <strong className={earn.rankAmount}>{money(value)}</strong>\n <span className={earn.rankSub}>\n {count(m.count)}\n {topTotal > 0 ? ` · ${Math.round((value / topTotal) * 100)}% of these` : ''}\n </span>\n <span className={earn.rankBar} aria-hidden>\n <span\n className={earn.rankFill}\n style={{\n width: `${widest > 0 ? Math.max(2, (value / widest) * 100) : 0}%`,\n background: colors.series[i % colors.series.length],\n }}\n />\n </span>\n </li>\n );\n })}\n </ol>\n )}\n </Panel>\n </div>\n </div>\n </>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/admin/revenue/earnings/earnings-summary.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport Link from 'next/link';\nimport { TimeSeriesChart } from '@withpotter/components/charts';\nimport type { PlatformRevenue } from '@withpotter/lib/admin-api';\nimport type { EarningsCurrency } from '@withpotter/lib/admin-earnings-api';\nimport {\n earningsFigures,\n pctChange,\n toMajor,\n type EarningsPart,\n} from '@withpotter/lib/admin-earnings-model';\nimport { LoadFailed } from '../../audit/load-failed';\nimport { AdmLoading, Panel, StatCard, moneyIn, toTrend } from '../../_ui';\nimport { useSourceColors } from './earnings-colors';\nimport { CompareBars, PieWithDetails } from './earnings-legend';\nimport { useEarnings } from './use-earnings';\nimport styles from '../revenue.module.css';\nimport ledger from '../ledger.module.css';\nimport earn from './earnings.module.css';\n\nconst integer = new Intl.NumberFormat('en-US');\n\n/** The streams as the owner names them, in the order they are read. */\nconst ORDER = ['online', 'pos', 'payouts', 'settlements', 'cash', 'plans', 'payments', 'other'] as const;\n\n/** Streams that always have a card, even at zero; the rest appear when they earned something. */\nconst ALWAYS = new Set(['online', 'pos', 'payouts', 'settlements', 'cash', 'plans']);\n\nconst TITLES: Record<string, string> = {\n online: 'Online card payments',\n pos: 'POS card payments and transfers',\n payouts: 'Payouts',\n settlements: \"Settlements to merchants' banks\",\n cash: 'Cash received',\n plans: 'Plan subscriptions',\n payments: 'Other card payments',\n other: 'Other fees',\n};\n\nconst WHAT: Record<string, string> = {\n online: 'What Olive keeps when a customer pays a merchant by card online, including money that arrives through the payment gateway.',\n pos: 'What Olive keeps when a customer pays at a merchant\\'s POS terminal, by card or by transfer.',\n payouts: 'What Olive charges when a merchant sends money out of their wallet.',\n settlements: 'What Olive charges when a merchant\\'s money is settled to their bank account.',\n cash: 'What Olive charges when cash is received for a merchant.',\n plans: 'What merchants pay Olive for their plan, however they paid.',\n payments: 'Card payment fees that are not tagged as online or POS.',\n other: 'Fees from anything not listed here.',\n};\n\ntype Metric = 'earned' | 'costs' | 'profit';\n\nconst NOTHING_YET = (currency: string): EarningsCurrency => ({\n currency,\n exponent: 2,\n feeMinor: '0',\n providerMinor: '0',\n netMinor: '0',\n previousFeeMinor: '0',\n previousProviderMinor: '0',\n count: 0,\n providerCostsRecorded: false,\n bySource: [],\n series: [],\n previousSeries: [],\n topMerchants: [],\n});\n\nconst MINOR = new Set(['transfers', 'fx', 'lending', 'other']);\n\n/** Anything outside the named streams is one \"Other fees\" part, so the parts still add up to the total. */\nfunction named(parts: EarningsPart[]): EarningsPart[] {\n const rest = parts.filter((p) => MINOR.has(p.source));\n const main = parts.filter((p) => !MINOR.has(p.source));\n const sum = (k: 'value' | 'previous' | 'count') => rest.reduce((a, p) => a + p[k], 0);\n\n if (sum('value') <= 0 && sum('previous') <= 0) return main;\n\n return [\n ...main,\n { source: 'other', label: 'Other fees', value: sum('value'), previous: sum('previous'), count: sum('count') },\n ];\n}\n\n/** A card that picks which measure the chart shows, like the metric tabs of an analytics page. */\nfunction MetricCard({\n on,\n onPick,\n span,\n children,\n}: {\n on: boolean;\n onPick: () => void;\n span: string;\n children: React.ReactNode;\n}) {\n return (\n <div\n className={`${span} ${earn.metric} ${on ? earn.metricOn : ''}`}\n role=\"button\"\n tabIndex={0}\n aria-pressed={on}\n onClick={onPick}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onPick();\n }\n }}\n >\n {children}\n </div>\n );\n}\n\nconst count = (n: number) => `${integer.format(n)} ${n === 1 ? 'transaction' : 'transactions'}`;\n\n/**\n * What Olive earns, as the owner reads it: the total, what it cost, what is left, each stream on its own card, where it\n * comes from, how it moves and which merchants it comes from. One currency at a time, never a mix.\n */\nexport function EarningsSummary({\n startDate,\n endDate,\n currency,\n recurring,\n span,\n}: {\n startDate?: string;\n endDate?: string;\n /** The currency filter's choice; empty follows the billing currency. */\n currency: string;\n recurring: PlatformRevenue['recurring'];\n span: string;\n}) {\n const { data, isLoading, isError, refetch } = useEarnings({ startDate, endDate });\n const colors = useSourceColors();\n const [metric, setMetric] = useState<Metric>('earned');\n\n if (isError)\n return (\n <LoadFailed\n message=\"Earnings could not be loaded. This box may not have them yet.\"\n onRetry={() => void refetch()}\n />\n );\n\n if (isLoading || !data) return <AdmLoading label=\"Loading what Olive earns…\" />;\n\n const wanted = currency || recurring.currency || 'NGN';\n // Nothing earned yet is a real answer: every card stays on the page, at zero, until the first fee comes in.\n const cur = data.currencies.find((c) => c.currency === wanted) ?? data.currencies[0] ?? NOTHING_YET(wanted);\n\n const plan = recurring.currency === cur.currency ? (recurring.collected ?? null) : null;\n const f = earningsFigures(cur, plan);\n const have = named(f.parts);\n const parts = ORDER.filter((k) => ALWAYS.has(k) || have.some((p) => p.source === k && (p.value !== 0 || p.previous !== 0)))\n .map(\n (k): EarningsPart =>\n have.find((p) => p.source === k) ?? { source: k, label: TITLES[k], value: 0, previous: 0, count: 0 },\n )\n .sort((a, b) => ORDER.indexOf(a.source as never) - ORDER.indexOf(b.source as never));\n const money = moneyIn(cur.currency, 2);\n const caption = `${span[0].toUpperCase()}${span.slice(1)} · ${cur.currency}`;\n const margin = f.gross > 0 ? Math.round((f.net / f.gross) * 1000) / 10 : null;\n const green = colors.series[2];\n const red = colors.series[7];\n const byMethod = (recurring.collectedByMethod ?? []).filter((m) => m.amount > 0);\n const day = (p: { feeMinor: string; providerMinor: string }) => {\n const fee = toMajor(p.feeMinor, cur.exponent);\n const cost = toMajor(p.providerMinor, cur.exponent);\n\n return metric === 'earned' ? fee : metric === 'costs' ? cost : fee - cost;\n };\n const trend = toTrend(cur.series.map((p) => ({ date: p.date, value: day(p) })), 'value');\n const before = toTrend(cur.previousSeries.map((p) => ({ date: p.date, value: day(p) })), 'value');\n const feeSpark = cur.series.map((p) => toMajor(p.feeMinor, cur.exponent));\n const chartTitle = { earned: 'Fees earned each day', costs: 'Processing costs each day', profit: 'Profit each day' }[metric];\n const topTotal = cur.topMerchants.reduce((a, m) => a + toMajor(m.feeMinor, cur.exponent), 0);\n const label = (p: EarningsPart) => TITLES[p.source] ?? p.label;\n\n return (\n <>\n <p className={ledger.sectionNote}>\n How money flows through Olive: what it earned, what it cost, what is left, and where it came from. Showing{' '}\n {cur.currency}\n {data.currencies.length > 1 ? '; use the currency filter for the others.' : '.'}\n </p>\n\n <section aria-label=\"Earned, costs and profit\" className=\"adm-grid\">\n <MetricCard on={metric === 'earned'} onPick={() => setMetric('earned')} span=\"adm-span-3\">\n <StatCard\n label=\"Total earned\"\n accent={colors.series[0]}\n spark={feeSpark}\n sparkColor={colors.series[0]}\n value={money(f.gross)}\n sub=\"Fees plus plan income\"\n delta={{ pct: pctChange(f.gross, f.feesBefore + f.plan), caption: 'vs the period before' }}\n hint=\"Everything Olive earned in this period: fees on card payments, payouts, settlements and cash, plus what merchants paid for their plans.\"\n />\n </MetricCard>\n <MetricCard on={metric === 'costs'} onPick={() => setMetric('costs')} span=\"adm-span-3\">\n <StatCard\n label=\"Processing costs\"\n accent={red}\n value={money(cur.providerCostsRecorded ? f.provider : 0)}\n sub={cur.providerCostsRecorded ? 'Charged by the processors' : 'None recorded yet'}\n hint=\"What the payment processors kept from the same payments, as far as the system records it. Until costs are recorded this stays at zero and profit equals what was earned.\"\n />\n </MetricCard>\n <MetricCard on={metric === 'profit'} onPick={() => setMetric('profit')} span=\"adm-span-3\">\n <StatCard\n label=\"Profit\"\n accent={green}\n value={money(f.net)}\n sub={cur.providerCostsRecorded ? 'After processing costs' : 'Same as earned until costs are recorded'}\n hint=\"Total earned less the processing costs the system records. Running costs such as hosting and staff are not included.\"\n />\n </MetricCard>\n <div className=\"adm-span-3\">\n <StatCard\n label=\"Margin\"\n accent={colors.series[3]}\n value={margin === null ? '–' : `${margin}%`}\n sub=\"Profit as a share of earned\"\n hint=\"Profit divided by total earned.\"\n />\n </div>\n </section>\n\n <div className=\"adm-grid\">\n <div className=\"adm-span-12\">\n <Panel title={chartTitle} caption={`${caption} · dotted line is the period before`} height=\"calc(var(--adm-panel-h) + 32px)\">\n <div className={styles.flatChart}>\n <TimeSeriesChart\n data={trend}\n color={metric === 'costs' ? red : metric === 'profit' ? green : colors.series[0]}\n formatValue={money}\n compare={before}\n compareLabel=\"Period before\"\n emptyMessage=\"Nothing was recorded on any day in this period\"\n height={240}\n />\n </div>\n </Panel>\n </div>\n </div>\n\n <h2 className={ledger.sectionTitle}>Where the money comes from</h2>\n <section aria-label=\"Earnings by stream\" className=\"adm-grid\">\n {parts.map((p) => (\n <div key={p.source} className=\"adm-span-4\">\n <StatCard\n label={label(p)}\n accent={colors[p.source]}\n value={money(p.value)}\n sub={p.source === 'plans' ? 'Collected in this period' : p.count === 0 ? 'Nothing in this period' : count(p.count)}\n hint={WHAT[p.source]}\n delta={\n p.source === 'plans'\n ? undefined\n : { pct: pctChange(p.value, p.previous), caption: 'vs the period before' }\n }\n />\n </div>\n ))}\n <div className=\"adm-span-4\">\n <StatCard\n label=\"Olive account and outlets\"\n accent=\"var(--text-tertiary)\"\n value=\"Not counted yet\"\n sub=\"No earnings recorded\"\n hint=\"Money received through the Olive account and outlets. The bank system does not report a fee for it yet, so nothing can be added here.\"\n />\n </div>\n </section>\n\n <div className=\"adm-grid\">\n <div className=\"adm-span-4\">\n <Panel title=\"Where it comes from\" caption={caption} height=\"auto\">\n {parts.some((p) => p.value > 0) ? (\n <PieWithDetails\n money={money}\n centerLabel=\"Earned\"\n slices={parts.map((p) => ({\n key: p.source,\n label: label(p),\n value: p.value,\n color: colors[p.source],\n detail: p.source === 'plans' ? 'Collected' : count(p.count),\n }))}\n />\n ) : (\n <p className={styles.empty}>Nothing earned in this period.</p>\n )}\n </Panel>\n </div>\n <div className=\"adm-span-4\">\n <Panel title=\"What Olive keeps\" caption={caption} height=\"auto\">\n {cur.providerCostsRecorded && f.gross > 0 ? (\n <PieWithDetails\n money={money}\n centerLabel=\"Earned\"\n slices={[\n { key: 'profit', label: 'Profit', value: Math.max(0, f.net), color: green, detail: 'After processing costs' },\n { key: 'cost', label: 'Processing costs', value: Math.max(0, f.provider), color: red, detail: 'Charged by the processors' },\n ]}\n />\n ) : (\n <p className={styles.empty}>\n No processing cost is recorded for these fees yet, so profit equals what was earned.\n </p>\n )}\n </Panel>\n </div>\n <div className=\"adm-span-4\">\n <Panel title=\"How plans were paid\" caption={caption} height=\"auto\">\n {byMethod.length > 0 && recurring.currency === cur.currency ? (\n <PieWithDetails\n money={money}\n centerLabel=\"Plans\"\n slices={byMethod.map((m, i) => ({\n key: m.method,\n label: m.method,\n value: m.amount,\n color: colors.series[i % colors.series.length],\n detail: `${integer.format(m.payments)} ${m.payments === 1 ? 'payment' : 'payments'}`,\n }))}\n />\n ) : (\n <p className={styles.empty}>No plan payments in this period.</p>\n )}\n </Panel>\n </div>\n </div>\n\n <div className=\"adm-grid\">\n <div className=\"adm-span-6\">\n <Panel title=\"Stream by stream, against the period before\" caption={caption} height=\"auto\">\n <CompareBars\n money={money}\n rows={parts\n .filter((p) => p.source !== 'plans')\n .map((p) => ({\n key: p.source,\n label: label(p),\n color: colors[p.source],\n value: p.value,\n previous: p.previous,\n }))}\n />\n </Panel>\n </div>\n <div className=\"adm-span-6\">\n <Panel title=\"The merchants it comes from\" caption={`Fees, ${caption}`} height=\"auto\">\n {cur.topMerchants.length === 0 ? (\n <p className={styles.empty}>No fees to show in this period.</p>\n ) : (\n <ol className={earn.rankList}>\n {cur.topMerchants.map((m, i) => {\n const value = toMajor(m.feeMinor, cur.exponent);\n const widest = toMajor(cur.topMerchants[0].feeMinor, cur.exponent);\n\n return (\n <li key={`${m.tenantId ?? 'none'}-${i}`} className={earn.rankRow}>\n <span className={earn.rankName}>\n {m.tenantId && m.name ? (\n <Link href={`/admin/tenants/${m.tenantId}`}>{m.name}</Link>\n ) : (\n 'No merchant recorded'\n )}\n </span>\n <strong className={earn.rankAmount}>{money(value)}</strong>\n <span className={earn.rankSub}>\n {count(m.count)}\n {topTotal > 0 ? ` · ${Math.round((value / topTotal) * 100)}% of these` : ''}\n </span>\n <span className={earn.rankBar} aria-hidden>\n <span\n className={earn.rankFill}\n style={{\n width: `${widest > 0 ? Math.max(2, (value / widest) * 100) : 0}%`,\n background: colors.series[i % colors.series.length],\n }}\n />\n </span>\n </li>\n );\n })}\n </ol>\n )}\n </Panel>\n </div>\n </div>\n </>\n );\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@withpotter/pages-admin",
|
|
3
|
-
"version": "0.13.0-next.
|
|
3
|
+
"version": "0.13.0-next.124",
|
|
4
4
|
"description": "Box console platform-admin routes: overview, revenue, accounts, tenants, admins, roles, audit.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@radix-ui/react-popover": "^1.1.23",
|
|
33
33
|
"@tanstack/react-query": "^5.90.20",
|
|
34
|
-
"@withpotter/lib": "^0.13.0-next.
|
|
35
|
-
"@withpotter/platform-admin": "^0.13.0-next.
|
|
36
|
-
"@withpotter/ui-kit": "^0.13.0-next.
|
|
34
|
+
"@withpotter/lib": "^0.13.0-next.124",
|
|
35
|
+
"@withpotter/platform-admin": "^0.13.0-next.124",
|
|
36
|
+
"@withpotter/ui-kit": "^0.13.0-next.124",
|
|
37
37
|
"lucide-react": "^0.562.0",
|
|
38
38
|
"motion": "^12.42.2"
|
|
39
39
|
},
|