@withpotter/pages-admin 0.13.0-next.124 → 0.13.0-next.58
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.
- package/dist/admin/_money/cash-postings.js +3 -20
- package/dist/admin/_money/cash-postings.js.map +1 -1
- package/dist/admin/_money/cash.module.css +0 -10
- package/dist/admin/_money/finder-format.js +0 -1
- package/dist/admin/_money/finder-format.js.map +1 -1
- package/dist/admin/_money/format.js +0 -1
- package/dist/admin/_money/format.js.map +1 -1
- package/dist/admin/_overview/activation-panel.js +6 -6
- package/dist/admin/_overview/activation-panel.js.map +1 -1
- package/dist/admin/_overview/attention-groups.js +8 -8
- package/dist/admin/_overview/attention-groups.js.map +1 -1
- package/dist/admin/_overview/attention-panel.js +33 -37
- package/dist/admin/_overview/attention-panel.js.map +1 -1
- package/dist/admin/_overview/attention.module.css +3 -0
- package/dist/admin/_overview/control-room.js +13 -41
- package/dist/admin/_overview/control-room.js.map +1 -1
- package/dist/admin/_overview/kpi-row.js +7 -7
- package/dist/admin/_overview/kpi-row.js.map +1 -1
- package/dist/admin/_overview/leaders-panel.js +1 -1
- package/dist/admin/_overview/leaders-panel.js.map +1 -1
- package/dist/admin/_overview/overdue-outstanding.js +1 -2
- package/dist/admin/_overview/overdue-outstanding.js.map +1 -1
- package/dist/admin/_overview/portfolio-panel.js +5 -5
- package/dist/admin/_overview/portfolio-panel.js.map +1 -1
- package/dist/admin/_overview/position-panel.js +12 -8
- package/dist/admin/_overview/position-panel.js.map +1 -1
- package/dist/admin/_pos/pos-figures.js +5 -18
- package/dist/admin/_pos/pos-figures.js.map +1 -1
- package/dist/admin/_pos/pos-rules.js +0 -2
- package/dist/admin/_pos/pos-rules.js.map +1 -1
- package/dist/admin/_pos/pos-words.js +1 -6
- package/dist/admin/_pos/pos-words.js.map +1 -1
- package/dist/admin/_pos/use-pos.js +1 -70
- package/dist/admin/_pos/use-pos.js.map +1 -1
- package/dist/admin/_shell/nav-config.js +3 -3
- package/dist/admin/_shell/nav-config.js.map +1 -1
- package/dist/admin/_shell/nav-filter.js +2 -4
- package/dist/admin/_shell/nav-filter.js.map +1 -1
- package/dist/admin/_shell/nav-row.js +2 -5
- package/dist/admin/_shell/nav-row.js.map +1 -1
- package/dist/admin/_shell/sidebar-footer.js +0 -20
- package/dist/admin/_shell/sidebar-footer.js.map +1 -1
- package/dist/admin/_shell/sidebar-footer.module.css +0 -17
- package/dist/admin/_shell/sidebar.js +15 -25
- package/dist/admin/_shell/sidebar.js.map +1 -1
- package/dist/admin/_shell/sidebar.module.css +0 -69
- package/dist/admin/_shell/use-fluid-hover.js +6 -37
- package/dist/admin/_shell/use-fluid-hover.js.map +1 -1
- package/dist/admin/_ui/admin.css +0 -7
- package/dist/admin/_ui/data-table.js +1 -1
- package/dist/admin/_ui/data-table.js.map +1 -1
- package/dist/admin/_ui/list-card.js +1 -1
- package/dist/admin/_ui/list-card.js.map +1 -1
- package/dist/admin/_ui/list-card.module.css +16 -2
- package/dist/admin/_ui/money-view.js +0 -1
- package/dist/admin/_ui/money-view.js.map +1 -1
- package/dist/admin/_ui/page-header.js +4 -31
- package/dist/admin/_ui/page-header.js.map +1 -1
- package/dist/admin/_ui/panel.module.css +10 -0
- package/dist/admin/_ui/stat-card.js +2 -11
- package/dist/admin/_ui/stat-card.js.map +1 -1
- package/dist/admin/_ui/trend.js +2 -2
- package/dist/admin/_ui/trend.js.map +1 -1
- package/dist/admin/_ui/workspace-tab.js +1 -11
- package/dist/admin/_ui/workspace-tab.js.map +1 -1
- package/dist/admin/accounts/account-panel.js +6 -49
- package/dist/admin/accounts/account-panel.js.map +1 -1
- package/dist/admin/accounts/account-panel.module.css +0 -73
- package/dist/admin/accounts/account-row.js +2 -11
- package/dist/admin/accounts/account-row.js.map +1 -1
- package/dist/admin/accounts/account-vitals.js +13 -15
- package/dist/admin/accounts/account-vitals.js.map +1 -1
- package/dist/admin/accounts/accounts-table.js +8 -39
- package/dist/admin/accounts/accounts-table.js.map +1 -1
- package/dist/admin/accounts/accounts-workspace.js +2 -2
- package/dist/admin/accounts/accounts-workspace.js.map +1 -1
- package/dist/admin/accounts/owner-session.js +6 -0
- package/dist/admin/accounts/owner-session.js.map +1 -1
- package/dist/admin/accounts/rows.module.css +0 -17
- package/dist/admin/audit/status-pill.js +0 -6
- package/dist/admin/audit/status-pill.js.map +1 -1
- package/dist/admin/audit/status-pill.module.css +0 -4
- package/dist/admin/commerce/commerce-overview.js +2 -2
- package/dist/admin/commerce/commerce-overview.js.map +1 -1
- package/dist/admin/commerce/merchant-meta.js +0 -1
- package/dist/admin/commerce/merchant-meta.js.map +1 -1
- package/dist/admin/commerce/merchant-view.js +2 -2
- package/dist/admin/commerce/merchant-view.js.map +1 -1
- package/dist/admin/feedback/prompt-operations.module.css +2 -0
- package/dist/admin/feedback/prompt-preview.module.css +2 -0
- package/dist/admin/hooks.js +1 -57
- package/dist/admin/hooks.js.map +1 -1
- package/dist/admin/layout.js +2 -8
- package/dist/admin/layout.js.map +1 -1
- package/dist/admin/page.js +8 -28
- package/dist/admin/page.js.map +1 -1
- package/dist/admin/payout-tiers/_tier-dialog.js +21 -31
- package/dist/admin/payout-tiers/_tier-dialog.js.map +1 -1
- package/dist/admin/payout-tiers/tier-preview-result.js +9 -10
- package/dist/admin/payout-tiers/tier-preview-result.js.map +1 -1
- package/dist/admin/payout-tiers/tiers-table.js +17 -40
- package/dist/admin/payout-tiers/tiers-table.js.map +1 -1
- package/dist/admin/payout-tiers/tiers-workspace.js +17 -20
- package/dist/admin/payout-tiers/tiers-workspace.js.map +1 -1
- package/dist/admin/pos/pos-workspace.js +2 -6
- package/dist/admin/pos/pos-workspace.js.map +1 -1
- package/dist/admin/pos/shifts-tab.js +9 -16
- package/dist/admin/pos/shifts-tab.js.map +1 -1
- package/dist/admin/revenue/cohort-panel.js +6 -6
- package/dist/admin/revenue/cohort-panel.js.map +1 -1
- package/dist/admin/revenue/health-panel.js +21 -22
- package/dist/admin/revenue/health-panel.js.map +1 -1
- package/dist/admin/revenue/plan-mix-panel.js +3 -3
- package/dist/admin/revenue/plan-mix-panel.js.map +1 -1
- package/dist/admin/revenue/quality-panel.js +19 -19
- package/dist/admin/revenue/quality-panel.js.map +1 -1
- package/dist/admin/revenue/revenue-kpis.js +50 -111
- package/dist/admin/revenue/revenue-kpis.js.map +1 -1
- package/dist/admin/revenue/revenue-overview.js +46 -39
- package/dist/admin/revenue/revenue-overview.js.map +1 -1
- package/dist/admin/revenue/revenue-retention.js +13 -17
- package/dist/admin/revenue/revenue-retention.js.map +1 -1
- package/dist/admin/revenue/revenue-workspace.js +6 -53
- package/dist/admin/revenue/revenue-workspace.js.map +1 -1
- package/dist/admin/revenue/subscriptions-panel.js +14 -14
- package/dist/admin/revenue/subscriptions-panel.js.map +1 -1
- package/dist/admin/revenue/top-tenants-panel.js +4 -4
- package/dist/admin/revenue/top-tenants-panel.js.map +1 -1
- package/dist/admin/settings/account-tab.js +25 -1
- package/dist/admin/settings/account-tab.js.map +1 -1
- package/dist/admin/settings/general-tab.js +2 -2
- package/dist/admin/settings/general-tab.js.map +1 -1
- package/dist/admin/settings/system-status-card.js +30 -0
- package/dist/admin/settings/system-status-card.js.map +1 -0
- package/dist/admin/support/queue-card.module.css +4 -1
- package/dist/admin/tenants/[id]/commerce/format.js +1 -2
- package/dist/admin/tenants/[id]/commerce/format.js.map +1 -1
- package/dist/admin/tenants/[id]/commerce/recent-orders.js +1 -1
- package/dist/admin/tenants/[id]/commerce/recent-orders.js.map +1 -1
- package/dist/admin/tenants/[id]/page.js +10 -37
- package/dist/admin/tenants/[id]/page.js.map +1 -1
- package/dist/admin/tenants/[id]/tenant-layout.module.css +4 -1
- package/dist/admin/tenants/[id]/tenant-overview.js +2 -3
- package/dist/admin/tenants/[id]/tenant-overview.js.map +1 -1
- package/dist/admin/tenants/[id]/tenant-overview.module.css +1 -1
- package/dist/admin/tenants/[id]/tenant-pos.js +16 -64
- package/dist/admin/tenants/[id]/tenant-pos.js.map +1 -1
- package/dist/admin/tenants/[id]/tenant-wallet-card.js +2 -3
- package/dist/admin/tenants/[id]/tenant-wallet-card.js.map +1 -1
- package/dist/admin/tenants/[id]/verification/document-viewer.js +52 -3
- package/dist/admin/tenants/[id]/verification/document-viewer.js.map +1 -1
- package/dist/admin/tenants/[id]/verification/document-viewer.module.css +0 -51
- package/dist/admin/tenants/[id]/verification/tenant-verification.js +0 -10
- package/dist/admin/tenants/[id]/verification/tenant-verification.js.map +1 -1
- package/dist/admin/tenants/tenant-list-row.js +1 -2
- package/dist/admin/tenants/tenant-list-row.js.map +1 -1
- package/dist/admin/tenants/tenants-table.js +2 -3
- package/dist/admin/tenants/tenants-table.js.map +1 -1
- package/dist/admin/tenants/tenants-workspace.js +2 -2
- package/dist/admin/tenants/tenants-workspace.js.map +1 -1
- package/dist/admin/verification/verification-page.js +3 -19
- package/dist/admin/verification/verification-page.js.map +1 -1
- package/dist/admin/verification/verification.module.css +0 -10
- package/dist/admin/verification/waiting-case-actions.js +68 -0
- package/dist/admin/verification/waiting-case-actions.js.map +1 -0
- package/dist/admin/verification/waiting-cases.js +48 -66
- package/dist/admin/verification/waiting-cases.js.map +1 -1
- package/package.json +5 -5
- package/dist/admin/_pos/olive-balance.js +0 -40
- package/dist/admin/_pos/olive-balance.js.map +0 -1
- package/dist/admin/_pos/olive-state.js +0 -23
- package/dist/admin/_pos/olive-state.js.map +0 -1
- package/dist/admin/_pos/payments-model.js +0 -55
- package/dist/admin/_pos/payments-model.js.map +0 -1
- package/dist/admin/_pos/tenant-operators.js +0 -63
- package/dist/admin/_pos/tenant-operators.js.map +0 -1
- package/dist/admin/_pos/tenant-outlets.js +0 -77
- package/dist/admin/_pos/tenant-outlets.js.map +0 -1
- package/dist/admin/_pos/tenant-pos-figures.js +0 -82
- package/dist/admin/_pos/tenant-pos-figures.js.map +0 -1
- package/dist/admin/_pos/tenant-requests.js +0 -64
- package/dist/admin/_pos/tenant-requests.js.map +0 -1
- package/dist/admin/_pos/tenant-transactions.js +0 -145
- package/dist/admin/_pos/tenant-transactions.js.map +0 -1
- package/dist/admin/_shell/nav-fold.js +0 -14
- package/dist/admin/_shell/nav-fold.js.map +0 -1
- package/dist/admin/_shell/nav-group.js +0 -78
- package/dist/admin/_shell/nav-group.js.map +0 -1
- package/dist/admin/_shell/use-open-groups.js +0 -64
- package/dist/admin/_shell/use-open-groups.js.map +0 -1
- package/dist/admin/_ui/page-header.module.css +0 -45
- package/dist/admin/accounts/account-sessions.js +0 -70
- package/dist/admin/accounts/account-sessions.js.map +0 -1
- package/dist/admin/accounts/account-sessions.module.css +0 -55
- package/dist/admin/accounts/account-stage-filter.js +0 -29
- package/dist/admin/accounts/account-stage-filter.js.map +0 -1
- package/dist/admin/accounts/account-stage.js +0 -81
- package/dist/admin/accounts/account-stage.js.map +0 -1
- package/dist/admin/accounts/hint.js +0 -12
- package/dist/admin/accounts/hint.js.map +0 -1
- package/dist/admin/payout-tiers/requirement-labels.js +0 -70
- package/dist/admin/payout-tiers/requirement-labels.js.map +0 -1
- package/dist/admin/pos/transactions-tab.js +0 -214
- package/dist/admin/pos/transactions-tab.js.map +0 -1
- package/dist/admin/revenue/channel-panel.js +0 -49
- package/dist/admin/revenue/channel-panel.js.map +0 -1
- package/dist/admin/revenue/channel-rows.js +0 -17
- package/dist/admin/revenue/channel-rows.js.map +0 -1
- package/dist/admin/revenue/download-button.js +0 -40
- package/dist/admin/revenue/download-button.js.map +0 -1
- package/dist/admin/revenue/earnings/earnings-colors.js +0 -25
- package/dist/admin/revenue/earnings/earnings-colors.js.map +0 -1
- package/dist/admin/revenue/earnings/earnings-legend.js +0 -75
- package/dist/admin/revenue/earnings/earnings-legend.js.map +0 -1
- package/dist/admin/revenue/earnings/earnings-summary.js +0 -308
- package/dist/admin/revenue/earnings/earnings-summary.js.map +0 -1
- package/dist/admin/revenue/earnings/earnings-table.js +0 -143
- package/dist/admin/revenue/earnings/earnings-table.js.map +0 -1
- package/dist/admin/revenue/earnings/earnings.module.css +0 -201
- package/dist/admin/revenue/earnings/use-earnings.js +0 -27
- package/dist/admin/revenue/earnings/use-earnings.js.map +0 -1
- package/dist/admin/revenue/ledger-model.js +0 -104
- package/dist/admin/revenue/ledger-model.js.map +0 -1
- package/dist/admin/revenue/ledger.module.css +0 -144
- package/dist/admin/revenue/merchant-headline.js +0 -37
- package/dist/admin/revenue/merchant-headline.js.map +0 -1
- package/dist/admin/revenue/merchant-payments-view.js +0 -234
- package/dist/admin/revenue/merchant-payments-view.js.map +0 -1
- package/dist/admin/revenue/method-cards.js +0 -40
- package/dist/admin/revenue/method-cards.js.map +0 -1
- package/dist/admin/revenue/payment-drawer.js +0 -53
- package/dist/admin/revenue/payment-drawer.js.map +0 -1
- package/dist/admin/revenue/plan-lines.js +0 -36
- package/dist/admin/revenue/plan-lines.js.map +0 -1
- package/dist/admin/revenue/plan-payment-drawer.js +0 -42
- package/dist/admin/revenue/plan-payment-drawer.js.map +0 -1
- package/dist/admin/revenue/plans-panel.js +0 -48
- package/dist/admin/revenue/plans-panel.js.map +0 -1
- package/dist/admin/revenue/platform-fees.js +0 -9
- package/dist/admin/revenue/platform-fees.js.map +0 -1
- package/dist/admin/revenue/print-summary-button.js +0 -27
- package/dist/admin/revenue/print-summary-button.js.map +0 -1
- package/dist/admin/revenue/subscriptions-view.js +0 -213
- package/dist/admin/revenue/subscriptions-view.js.map +0 -1
- package/dist/admin/revenue/summary-download.js +0 -38
- package/dist/admin/revenue/summary-download.js.map +0 -1
- package/dist/admin/revenue/summary-print.js +0 -82
- package/dist/admin/revenue/summary-print.js.map +0 -1
- package/dist/admin/revenue/summary-rows.js +0 -44
- package/dist/admin/revenue/summary-rows.js.map +0 -1
- package/dist/admin/revenue/url-filters.js +0 -36
- package/dist/admin/revenue/url-filters.js.map +0 -1
- package/dist/admin/revenue/use-url-filters.js +0 -24
- package/dist/admin/revenue/use-url-filters.js.map +0 -1
- package/dist/admin/tenants/[id]/use-tenant-wallet.js +0 -23
- package/dist/admin/tenants/[id]/use-tenant-wallet.js.map +0 -1
- package/dist/admin/tenants/[id]/verification/document-file.js +0 -78
- package/dist/admin/tenants/[id]/verification/document-file.js.map +0 -1
- package/dist/admin/tenants/[id]/verification/sent-files.js +0 -33
- package/dist/admin/tenants/[id]/verification/sent-files.js.map +0 -1
- package/dist/admin/verification/preview-dialog.js +0 -111
- package/dist/admin/verification/preview-dialog.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
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":";AAmGI,SAqFA,UArFA,KAsFE,YAtFF;AAjGJ,SAAS,gBAAgB;AACzB,OAAO,UAAU;AACjB,SAAS,uBAAuB;AAGhC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP,SAAS,kBAAkB;AAC3B,SAAS,YAAY,OAAO,UAAU,SAAS,eAAe;AAC9D,SAAS,uBAAuB;AAChC,SAAS,aAAa,sBAAsB;AAC5C,SAAS,mBAAmB;AAC5B,OAAO,YAAY;AACnB,OAAO,YAAY;AACnB,OAAO,UAAU;AAEjB,MAAM,UAAU,IAAI,KAAK,aAAa,OAAO;AAG7C,MAAM,QAAQ,CAAC,UAAU,OAAO,WAAW,eAAe,QAAQ,SAAS,YAAY,OAAO;AAG9F,MAAM,SAAS,oBAAI,IAAI,CAAC,UAAU,OAAO,WAAW,eAAe,QAAQ,OAAO,CAAC;AAEnF,MAAM,SAAiC;AAAA,EACrC,QAAQ;AAAA,EACR,KAAK;AAAA,EACL,SAAS;AAAA,EACT,aAAa;AAAA,EACb,MAAM;AAAA,EACN,OAAO;AAAA,EACP,UAAU;AAAA,EACV,OAAO;AACT;AAEA,MAAM,OAA+B;AAAA,EACnC,QAAQ;AAAA,EACR,KAAK;AAAA,EACL,SAAS;AAAA,EACT,aAAa;AAAA,EACb,MAAM;AAAA,EACN,OAAO;AAAA,EACP,UAAU;AAAA,EACV,OAAO;AACT;AAIA,MAAM,cAAc,CAAC,cAAwC;AAAA,EAC3D;AAAA,EACA,UAAU;AAAA,EACV,UAAU;AAAA,EACV,eAAe;AAAA,EACf,UAAU;AAAA,EACV,kBAAkB;AAAA,EAClB,uBAAuB;AAAA,EACvB,OAAO;AAAA,EACP,uBAAuB;AAAA,EACvB,UAAU,CAAC;AAAA,EACX,QAAQ,CAAC;AAAA,EACT,gBAAgB,CAAC;AAAA,EACjB,cAAc,CAAC;AACjB;AAEA,MAAM,QAAQ,oBAAI,IAAI,CAAC,aAAa,MAAM,WAAW,OAAO,CAAC;AAG7D,SAAS,MAAM,OAAuC;AACpD,QAAM,OAAO,MAAM,OAAO,CAAC,MAAM,MAAM,IAAI,EAAE,MAAM,CAAC;AACpD,QAAM,OAAO,MAAM,OAAO,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,MAAM,CAAC;AACrD,QAAM,MAAM,CAAC,MAAsC,KAAK,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,CAAC,GAAG,CAAC;AAEpF,MAAI,IAAI,OAAO,KAAK,KAAK,IAAI,UAAU,KAAK,EAAG,QAAO;AAEtD,SAAO;AAAA,IACL,GAAG;AAAA,IACH,EAAE,QAAQ,SAAS,OAAO,cAAc,OAAO,IAAI,OAAO,GAAG,UAAU,IAAI,UAAU,GAAG,OAAO,IAAI,OAAO,EAAE;AAAA,EAC9G;AACF;AAGA,SAAS,WAAW;AAAA,EAClB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKG;AACD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,GAAG,IAAI,IAAI,KAAK,MAAM,IAAI,KAAK,KAAK,WAAW,EAAE;AAAA,MAC5D,MAAK;AAAA,MACL,UAAU;AAAA,MACV,gBAAc;AAAA,MACd,SAAS;AAAA,MACT,WAAW,CAAC,MAAM;AAChB,YAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,KAAK;AACtC,YAAE,eAAe;AACjB,iBAAO;AAAA,QACT;AAAA,MACF;AAAA,MAEC;AAAA;AAAA,EACH;AAEJ;AAEA,MAAM,QAAQ,CAAC,MAAc,GAAG,QAAQ,OAAO,CAAC,CAAC,IAAI,MAAM,IAAI,gBAAgB,cAAc;AAMtF,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAOG;AACD,QAAM,EAAE,MAAM,WAAW,SAAS,QAAQ,IAAI,YAAY,EAAE,WAAW,QAAQ,CAAC;AAChF,QAAM,SAAS,gBAAgB;AAC/B,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAiB,QAAQ;AAErD,MAAI;AACF,WACE;AAAA,MAAC;AAAA;AAAA,QACC,SAAQ;AAAA,QACR,SAAS,MAAM,KAAK,QAAQ;AAAA;AAAA,IAC9B;AAGJ,MAAI,aAAa,CAAC,KAAM,QAAO,oBAAC,cAAW,OAAM,kCAA4B;AAE7E,QAAM,SAAS,YAAY,UAAU,YAAY;AAEjD,QAAM,MAAM,KAAK,WAAW,KAAK,CAAC,MAAM,EAAE,aAAa,MAAM,KAAK,KAAK,WAAW,CAAC,KAAK,YAAY,MAAM;AAE1G,QAAM,OAAO,UAAU,aAAa,IAAI,WAAY,UAAU,aAAa,OAAQ;AACnF,QAAM,IAAI,gBAAgB,KAAK,IAAI;AACnC,QAAM,OAAO,MAAM,EAAE,KAAK;AAC1B,QAAM,QAAQ,MAAM,OAAO,CAAC,MAAM,OAAO,IAAI,CAAC,KAAK,KAAK,KAAK,CAAC,MAAM,EAAE,WAAW,MAAM,EAAE,UAAU,KAAK,EAAE,aAAa,EAAE,CAAC,EACvH;AAAA,IACC,CAAC,MACC,KAAK,KAAK,CAAC,MAAM,EAAE,WAAW,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,OAAO,CAAC,GAAG,OAAO,GAAG,UAAU,GAAG,OAAO,EAAE;AAAA,EACvG,EACC,KAAK,CAAC,GAAG,MAAM,MAAM,QAAQ,EAAE,MAAe,IAAI,MAAM,QAAQ,EAAE,MAAe,CAAC;AACrF,QAAM,QAAQ,QAAQ,IAAI,UAAU,CAAC;AACrC,QAAM,UAAU,GAAG,KAAK,CAAC,EAAE,YAAY,CAAC,GAAG,KAAK,MAAM,CAAC,CAAC,SAAM,IAAI,QAAQ;AAC1E,QAAM,SAAS,EAAE,QAAQ,IAAI,KAAK,MAAO,EAAE,MAAM,EAAE,QAAS,GAAI,IAAI,KAAK;AACzE,QAAM,QAAQ,OAAO,OAAO,CAAC;AAC7B,QAAM,MAAM,OAAO,OAAO,CAAC;AAC3B,QAAM,YAAY,UAAU,qBAAqB,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAC/E,QAAM,MAAM,CAAC,MAAmD;AAC9D,UAAM,MAAM,QAAQ,EAAE,UAAU,IAAI,QAAQ;AAC5C,UAAM,OAAO,QAAQ,EAAE,eAAe,IAAI,QAAQ;AAElD,WAAO,WAAW,WAAW,MAAM,WAAW,UAAU,OAAO,MAAM;AAAA,EACvE;AACA,QAAM,QAAQ,QAAQ,IAAI,OAAO,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,IAAI,CAAC,EAAE,EAAE,GAAG,OAAO;AACvF,QAAM,SAAS,QAAQ,IAAI,eAAe,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,IAAI,CAAC,EAAE,EAAE,GAAG,OAAO;AAChG,QAAM,WAAW,IAAI,OAAO,IAAI,CAAC,MAAM,QAAQ,EAAE,UAAU,IAAI,QAAQ,CAAC;AACxE,QAAM,aAAa,EAAE,QAAQ,wBAAwB,OAAO,6BAA6B,QAAQ,kBAAkB,EAAE,MAAM;AAC3H,QAAM,WAAW,IAAI,aAAa,OAAO,CAAC,GAAG,MAAM,IAAI,QAAQ,EAAE,UAAU,IAAI,QAAQ,GAAG,CAAC;AAC3F,QAAM,QAAQ,CAAC,MAAoB,OAAO,EAAE,MAAM,KAAK,EAAE;AAEzD,SACE,iCACE;AAAA,yBAAC,OAAE,WAAW,OAAO,aAAa;AAAA;AAAA,MAC2E;AAAA,MAC1G,IAAI;AAAA,MACJ,KAAK,WAAW,SAAS,IAAI,8CAA8C;AAAA,OAC9E;AAAA,IAEA,qBAAC,aAAQ,cAAW,4BAA2B,WAAU,YACvD;AAAA,0BAAC,cAAW,IAAI,WAAW,UAAU,QAAQ,MAAM,UAAU,QAAQ,GAAG,MAAK,cAC3E;AAAA,QAAC;AAAA;AAAA,UACC,OAAM;AAAA,UACN,QAAQ,OAAO,OAAO,CAAC;AAAA,UACvB,OAAO;AAAA,UACP,YAAY,OAAO,OAAO,CAAC;AAAA,UAC3B,OAAO,MAAM,EAAE,KAAK;AAAA,UACpB,KAAI;AAAA,UACJ,OAAO,EAAE,KAAK,UAAU,EAAE,OAAO,EAAE,aAAa,EAAE,IAAI,GAAG,SAAS,uBAAuB;AAAA,UACzF,MAAK;AAAA;AAAA,MACP,GACF;AAAA,MACA,oBAAC,cAAW,IAAI,WAAW,SAAS,QAAQ,MAAM,UAAU,OAAO,GAAG,MAAK,cACzE;AAAA,QAAC;AAAA;AAAA,UACC,OAAM;AAAA,UACN,QAAQ;AAAA,UACR,OAAO,MAAM,IAAI,wBAAwB,EAAE,WAAW,CAAC;AAAA,UACvD,KAAK,IAAI,wBAAwB,8BAA8B;AAAA,UAC/D,MAAK;AAAA;AAAA,MACP,GACF;AAAA,MACA,oBAAC,cAAW,IAAI,WAAW,UAAU,QAAQ,MAAM,UAAU,QAAQ,GAAG,MAAK,cAC3E;AAAA,QAAC;AAAA;AAAA,UACC,OAAM;AAAA,UACN,QAAQ;AAAA,UACR,OAAO,MAAM,EAAE,GAAG;AAAA,UAClB,KAAK,IAAI,wBAAwB,2BAA2B;AAAA,UAC5D,MAAK;AAAA;AAAA,MACP,GACF;AAAA,MACA,oBAAC,SAAI,WAAU,cACb;AAAA,QAAC;AAAA;AAAA,UACC,OAAM;AAAA,UACN,QAAQ,OAAO,OAAO,CAAC;AAAA,UACvB,OAAO,WAAW,OAAO,WAAM,GAAG,MAAM;AAAA,UACxC,KAAI;AAAA,UACJ,MAAK;AAAA;AAAA,MACP,GACF;AAAA,OACF;AAAA,IAEA,oBAAC,SAAI,WAAU,YACb,8BAAC,SAAI,WAAU,eACb,8BAAC,SAAM,OAAO,YAAY,SAAS,GAAG,OAAO,0CAAuC,QAAO,mCACzF,8BAAC,SAAI,WAAW,OAAO,WACrB;AAAA,MAAC;AAAA;AAAA,QACC,MAAM;AAAA,QACN,OAAO,WAAW,UAAU,MAAM,WAAW,WAAW,QAAQ,OAAO,OAAO,CAAC;AAAA,QAC/E,aAAa;AAAA,QACb,SAAS;AAAA,QACT,cAAa;AAAA,QACb,cAAa;AAAA,QACb,QAAQ;AAAA;AAAA,IACV,GACF,GACF,GACF,GACF;AAAA,IAEA,oBAAC,QAAG,WAAW,OAAO,cAAc,wCAA0B;AAAA,IAC9D,qBAAC,aAAQ,cAAW,sBAAqB,WAAU,YAChD;AAAA,YAAM,IAAI,CAAC,MACV,oBAAC,SAAmB,WAAU,cAC5B;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,MAAM,CAAC;AAAA,UACd,QAAQ,OAAO,EAAE,MAAM;AAAA,UACvB,OAAO,MAAM,EAAE,KAAK;AAAA,UACpB,KAAK,EAAE,WAAW,UAAU,6BAA6B,EAAE,UAAU,IAAI,2BAA2B,MAAM,EAAE,KAAK;AAAA,UACjH,MAAM,KAAK,EAAE,MAAM;AAAA,UACnB,OACE,EAAE,WAAW,UACT,SACA,EAAE,KAAK,UAAU,EAAE,OAAO,EAAE,QAAQ,GAAG,SAAS,uBAAuB;AAAA;AAAA,MAE/E,KAZQ,EAAE,MAaZ,CACD;AAAA,MACD,oBAAC,SAAI,WAAU,cACb;AAAA,QAAC;AAAA;AAAA,UACC,OAAM;AAAA,UACN,QAAO;AAAA,UACP,OAAM;AAAA,UACN,KAAI;AAAA,UACJ,MAAK;AAAA;AAAA,MACP,GACF;AAAA,OACF;AAAA,IAEA,qBAAC,SAAI,WAAU,YACb;AAAA,0BAAC,SAAI,WAAU,cACb,8BAAC,SAAM,OAAM,uBAAsB,SAAkB,QAAO,QACzD,gBAAM,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC,IAC5B;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA,aAAY;AAAA,UACZ,QAAQ,MAAM,IAAI,CAAC,OAAO;AAAA,YACxB,KAAK,EAAE;AAAA,YACP,OAAO,MAAM,CAAC;AAAA,YACd,OAAO,EAAE;AAAA,YACT,OAAO,OAAO,EAAE,MAAM;AAAA,YACtB,QAAQ,EAAE,WAAW,UAAU,cAAc,MAAM,EAAE,KAAK;AAAA,UAC5D,EAAE;AAAA;AAAA,MACJ,IAEA,oBAAC,OAAE,WAAW,OAAO,OAAO,4CAA8B,GAE9D,GACF;AAAA,MACA,oBAAC,SAAI,WAAU,cACb,8BAAC,SAAM,OAAM,oBAAmB,SAAkB,QAAO,QACtD,cAAI,yBAAyB,EAAE,QAAQ,IACtC;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA,aAAY;AAAA,UACZ,QAAQ;AAAA,YACN,EAAE,KAAK,UAAU,OAAO,UAAU,OAAO,KAAK,IAAI,GAAG,EAAE,GAAG,GAAG,OAAO,OAAO,QAAQ,yBAAyB;AAAA,YAC5G,EAAE,KAAK,QAAQ,OAAO,oBAAoB,OAAO,KAAK,IAAI,GAAG,EAAE,QAAQ,GAAG,OAAO,KAAK,QAAQ,4BAA4B;AAAA,UAC5H;AAAA;AAAA,MACF,IAEA,oBAAC,OAAE,WAAW,OAAO,OAAO,kGAE5B,GAEJ,GACF;AAAA,MACA,oBAAC,SAAI,WAAU,cACb,8BAAC,SAAM,OAAM,uBAAsB,SAAkB,QAAO,QACzD,mBAAS,SAAS,KAAK,UAAU,aAAa,IAAI,WACjD;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA,aAAY;AAAA,UACZ,QAAQ,SAAS,IAAI,CAAC,GAAG,OAAO;AAAA,YAC9B,KAAK,EAAE;AAAA,YACP,OAAO,EAAE;AAAA,YACT,OAAO,EAAE;AAAA,YACT,OAAO,OAAO,OAAO,IAAI,OAAO,OAAO,MAAM;AAAA,YAC7C,QAAQ,GAAG,QAAQ,OAAO,EAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,IAAI,YAAY,UAAU;AAAA,UACpF,EAAE;AAAA;AAAA,MACJ,IAEA,oBAAC,OAAE,WAAW,OAAO,OAAO,8CAAgC,GAEhE,GACF;AAAA,OACF;AAAA,IAEA,qBAAC,SAAI,WAAU,YACb;AAAA,0BAAC,SAAI,WAAU,cACb,8BAAC,SAAM,OAAM,+CAA8C,SAAkB,QAAO,QAClF;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA,MAAM,MACH,OAAO,CAAC,MAAM,EAAE,WAAW,OAAO,EAClC,IAAI,CAAC,OAAO;AAAA,YACX,KAAK,EAAE;AAAA,YACP,OAAO,MAAM,CAAC;AAAA,YACd,OAAO,OAAO,EAAE,MAAM;AAAA,YACtB,OAAO,EAAE;AAAA,YACT,UAAU,EAAE;AAAA,UACd,EAAE;AAAA;AAAA,MACN,GACF,GACF;AAAA,MACA,oBAAC,SAAI,WAAU,cACb,8BAAC,SAAM,OAAM,+BAA8B,SAAS,SAAS,OAAO,IAAI,QAAO,QAC5E,cAAI,aAAa,WAAW,IAC3B,oBAAC,OAAE,WAAW,OAAO,OAAO,6CAA+B,IAE3D,oBAAC,QAAG,WAAW,KAAK,UACjB,cAAI,aAAa,IAAI,CAAC,GAAG,MAAM;AAC9B,cAAM,QAAQ,QAAQ,EAAE,UAAU,IAAI,QAAQ;AAC9C,cAAM,SAAS,QAAQ,IAAI,aAAa,CAAC,EAAE,UAAU,IAAI,QAAQ;AAEjE,eACE,qBAAC,QAAwC,WAAW,KAAK,SACvD;AAAA,8BAAC,UAAK,WAAW,KAAK,UACnB,YAAE,YAAY,EAAE,OACf,oBAAC,QAAK,MAAM,kBAAkB,EAAE,QAAQ,IAAK,YAAE,MAAK,IAEpD,wBAEJ;AAAA,UACA,oBAAC,YAAO,WAAW,KAAK,YAAa,gBAAM,KAAK,GAAE;AAAA,UAClD,qBAAC,UAAK,WAAW,KAAK,SACnB;AAAA,kBAAM,EAAE,KAAK;AAAA,YACb,WAAW,IAAI,SAAM,KAAK,MAAO,QAAQ,WAAY,GAAG,CAAC,eAAe;AAAA,aAC3E;AAAA,UACA,oBAAC,UAAK,WAAW,KAAK,SAAS,eAAW,MACxC;AAAA,YAAC;AAAA;AAAA,cACC,WAAW,KAAK;AAAA,cAChB,OAAO;AAAA,gBACL,OAAO,GAAG,SAAS,IAAI,KAAK,IAAI,GAAI,QAAQ,SAAU,GAAG,IAAI,CAAC;AAAA,gBAC9D,YAAY,OAAO,OAAO,IAAI,OAAO,OAAO,MAAM;AAAA,cACpD;AAAA;AAAA,UACF,GACF;AAAA,aArBO,GAAG,EAAE,YAAY,MAAM,IAAI,CAAC,EAsBrC;AAAA,MAEJ,CAAC,GACH,GAEJ,GACF;AAAA,OACF;AAAA,KACF;AAEJ;","names":[]}
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useEffect, useMemo, useState } from "react";
|
|
4
|
-
import Link from "next/link";
|
|
5
|
-
import { EmptyNote, TableRow, TableShell, Th } from "@withpotter/ui-kit";
|
|
6
|
-
import { adminEarningsApi } from "@withpotter/lib/admin-earnings-api";
|
|
7
|
-
import { formatMinor } from "@withpotter/lib/money-minor";
|
|
8
|
-
import { useMerchantOptions } from "../../hooks";
|
|
9
|
-
import { LoadFailed } from "../../audit/load-failed";
|
|
10
|
-
import { AdmLoading, FilterSearch, FilterSelect, Filters, Pagination } from "../../_ui";
|
|
11
|
-
import { DownloadButton } from "../download-button";
|
|
12
|
-
import { downloadName } from "../ledger-model";
|
|
13
|
-
import { useUrlFilters } from "../use-url-filters";
|
|
14
|
-
import { useSourceColors } from "./earnings-colors";
|
|
15
|
-
import { useEarningsTransactions } from "./use-earnings";
|
|
16
|
-
import styles from "../ledger.module.css";
|
|
17
|
-
import table from "../revenue.module.css";
|
|
18
|
-
const PAGE_SIZE = 25;
|
|
19
|
-
const FILTERS = { q: "", source: "", merchant: "" };
|
|
20
|
-
const SOURCES = [
|
|
21
|
-
{ value: "", label: "Every source" },
|
|
22
|
-
{ value: "online", label: "Online card payments" },
|
|
23
|
-
{ value: "pos", label: "POS card payments and transfers" },
|
|
24
|
-
{ value: "payments", label: "Other card payments" },
|
|
25
|
-
{ value: "payouts", label: "Payouts (money sent out)" },
|
|
26
|
-
{ value: "settlements", label: "Settlements to bank" },
|
|
27
|
-
{ value: "cash", label: "Cash received" },
|
|
28
|
-
{ value: "plans", label: "Plan subscriptions (from wallet)" },
|
|
29
|
-
{ value: "transfers", label: "Wallet transfers" },
|
|
30
|
-
{ value: "fx", label: "Currency conversion" },
|
|
31
|
-
{ value: "lending", label: "Lending" },
|
|
32
|
-
{ value: "other", label: "Other fees" }
|
|
33
|
-
];
|
|
34
|
-
function EarningsTable({ startDate, endDate }) {
|
|
35
|
-
const colors = useSourceColors();
|
|
36
|
-
const { values, page, set } = useUrlFilters("er", FILTERS);
|
|
37
|
-
const [search, setSearch] = useState(values.q);
|
|
38
|
-
useEffect(() => {
|
|
39
|
-
const timer = setTimeout(() => {
|
|
40
|
-
if (search.trim() !== values.q) set({ q: search.trim() });
|
|
41
|
-
}, 300);
|
|
42
|
-
return () => clearTimeout(timer);
|
|
43
|
-
}, [search]);
|
|
44
|
-
const filters = useMemo(
|
|
45
|
-
() => ({ startDate, endDate, source: values.source, merchantId: values.merchant, q: values.q, page, limit: PAGE_SIZE }),
|
|
46
|
-
[startDate, endDate, values, page]
|
|
47
|
-
);
|
|
48
|
-
const merchants = useMerchantOptions({ startDate, endDate });
|
|
49
|
-
const merchantChoices = [
|
|
50
|
-
{ value: "", label: "Every merchant" },
|
|
51
|
-
...(merchants.data?.merchants ?? []).map((one) => ({ value: one.id, label: one.name }))
|
|
52
|
-
];
|
|
53
|
-
const { data, isLoading, isError, refetch } = useEarningsTransactions(filters);
|
|
54
|
-
if (isError)
|
|
55
|
-
return /* @__PURE__ */ jsx(
|
|
56
|
-
LoadFailed,
|
|
57
|
-
{
|
|
58
|
-
message: "The earnings could not be loaded. This box may not have them yet.",
|
|
59
|
-
onRetry: () => void refetch()
|
|
60
|
-
}
|
|
61
|
-
);
|
|
62
|
-
if (isLoading || !data) return /* @__PURE__ */ jsx(AdmLoading, { label: "Loading what Olive earned\u2026" });
|
|
63
|
-
return /* @__PURE__ */ jsxs("div", { children: [
|
|
64
|
-
/* @__PURE__ */ jsx("p", { className: styles.sectionNote, children: "Every fee Olive earned in this period, and where it came from. Use the filters to narrow it, and download exactly what you see." }),
|
|
65
|
-
/* @__PURE__ */ jsxs("div", { className: styles.toolbar, children: [
|
|
66
|
-
/* @__PURE__ */ jsxs(Filters, { label: "Filter earnings", children: [
|
|
67
|
-
/* @__PURE__ */ jsx(
|
|
68
|
-
FilterSearch,
|
|
69
|
-
{
|
|
70
|
-
value: search,
|
|
71
|
-
onChange: setSearch,
|
|
72
|
-
placeholder: "Search a merchant, what it was for, or a source",
|
|
73
|
-
label: "Search earnings"
|
|
74
|
-
}
|
|
75
|
-
),
|
|
76
|
-
/* @__PURE__ */ jsx(FilterSelect, { value: values.source, onChange: (source) => set({ source }), options: SOURCES, label: "Source" }),
|
|
77
|
-
/* @__PURE__ */ jsx(
|
|
78
|
-
FilterSelect,
|
|
79
|
-
{
|
|
80
|
-
value: values.merchant,
|
|
81
|
-
onChange: (merchant) => set({ merchant }),
|
|
82
|
-
options: merchantChoices,
|
|
83
|
-
label: "Merchant",
|
|
84
|
-
searchable: true
|
|
85
|
-
}
|
|
86
|
-
)
|
|
87
|
-
] }),
|
|
88
|
-
/* @__PURE__ */ jsx(
|
|
89
|
-
DownloadButton,
|
|
90
|
-
{
|
|
91
|
-
filename: downloadName("olive-earnings", startDate, endDate),
|
|
92
|
-
load: () => adminEarningsApi.transactionsCsv({ ...filters, page: void 0, limit: void 0 })
|
|
93
|
-
}
|
|
94
|
-
)
|
|
95
|
-
] }),
|
|
96
|
-
data.rows.length === 0 ? /* @__PURE__ */ jsx(EmptyNote, { className: table.empty, children: "No earnings match. Try a wider date range or clear a filter." }) : /* @__PURE__ */ jsx(
|
|
97
|
-
TableShell,
|
|
98
|
-
{
|
|
99
|
-
minWidth: 960,
|
|
100
|
-
density: "compact",
|
|
101
|
-
head: /* @__PURE__ */ jsxs("tr", { children: [
|
|
102
|
-
/* @__PURE__ */ jsx(Th, { children: "When" }),
|
|
103
|
-
/* @__PURE__ */ jsx(Th, { children: "Source" }),
|
|
104
|
-
/* @__PURE__ */ jsx(Th, { children: "Merchant" }),
|
|
105
|
-
/* @__PURE__ */ jsx(Th, { children: "What it was for" }),
|
|
106
|
-
/* @__PURE__ */ jsx(Th, { align: "right", children: "Olive earned" }),
|
|
107
|
-
/* @__PURE__ */ jsx(Th, { align: "right", children: "Processor cost" }),
|
|
108
|
-
/* @__PURE__ */ jsx(Th, { align: "right", children: "Kept" })
|
|
109
|
-
] }),
|
|
110
|
-
children: data.rows.map((row) => /* @__PURE__ */ jsx(EarningRow, { row, color: colors[row.source] }, `${row.id}-${row.currency}`))
|
|
111
|
-
}
|
|
112
|
-
),
|
|
113
|
-
/* @__PURE__ */ jsx(
|
|
114
|
-
Pagination,
|
|
115
|
-
{
|
|
116
|
-
page,
|
|
117
|
-
totalPages: data.totalPages,
|
|
118
|
-
total: data.total,
|
|
119
|
-
noun: data.total === 1 ? "earning" : "earnings",
|
|
120
|
-
onPage: (next) => set({ page: next })
|
|
121
|
-
}
|
|
122
|
-
)
|
|
123
|
-
] });
|
|
124
|
-
}
|
|
125
|
-
function EarningRow({ row, color }) {
|
|
126
|
-
const money = (minor) => formatMinor(minor, row.currency);
|
|
127
|
-
return /* @__PURE__ */ jsxs(TableRow, { children: [
|
|
128
|
-
/* @__PURE__ */ jsx("td", { className: table.num, children: new Date(row.at).toLocaleString(void 0, { day: "numeric", month: "short", year: "numeric", hour: "2-digit", minute: "2-digit" }) }),
|
|
129
|
-
/* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: 8 }, children: [
|
|
130
|
-
/* @__PURE__ */ jsx("span", { "aria-hidden": true, style: { width: 10, height: 10, borderRadius: 3, background: color, flexShrink: 0 } }),
|
|
131
|
-
row.sourceLabel
|
|
132
|
-
] }) }),
|
|
133
|
-
/* @__PURE__ */ jsx("td", { className: table.name, children: row.merchant.id && row.merchant.name ? /* @__PURE__ */ jsx(Link, { href: `/admin/tenants/${row.merchant.id}`, children: row.merchant.name }) : "No merchant recorded" }),
|
|
134
|
-
/* @__PURE__ */ jsx("td", { children: row.description }),
|
|
135
|
-
/* @__PURE__ */ jsx("td", { className: table.numStrong, children: money(row.feeMinor) }),
|
|
136
|
-
/* @__PURE__ */ jsx("td", { className: table.num, children: row.providerMinor === "0" ? "\u2013" : money(row.providerMinor) }),
|
|
137
|
-
/* @__PURE__ */ jsx("td", { className: table.num, children: money(row.netMinor) })
|
|
138
|
-
] });
|
|
139
|
-
}
|
|
140
|
-
export {
|
|
141
|
-
EarningsTable
|
|
142
|
-
};
|
|
143
|
-
//# sourceMappingURL=earnings-table.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/admin/revenue/earnings/earnings-table.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useMemo, useState } from 'react';\nimport Link from 'next/link';\nimport { EmptyNote, TableRow, TableShell, Th } from '@withpotter/ui-kit';\nimport { adminEarningsApi, type EarningsRow } from '@withpotter/lib/admin-earnings-api';\nimport { formatMinor } from '@withpotter/lib/money-minor';\nimport { useMerchantOptions } from '../../hooks';\nimport { LoadFailed } from '../../audit/load-failed';\nimport { AdmLoading, FilterSearch, FilterSelect, Filters, Pagination } from '../../_ui';\nimport { DownloadButton } from '../download-button';\nimport { downloadName } from '../ledger-model';\nimport { useUrlFilters } from '../use-url-filters';\nimport { useSourceColors } from './earnings-colors';\nimport { useEarningsTransactions } from './use-earnings';\nimport styles from '../ledger.module.css';\nimport table from '../revenue.module.css';\n\nconst PAGE_SIZE = 25;\nconst FILTERS = { q: '', source: '', merchant: '' };\n\nconst SOURCES = [\n { value: '', label: 'Every source' },\n { value: 'online', label: 'Online card payments' },\n { value: 'pos', label: 'POS card payments and transfers' },\n { value: 'payments', label: 'Other card payments' },\n { value: 'payouts', label: 'Payouts (money sent out)' },\n { value: 'settlements', label: 'Settlements to bank' },\n { value: 'cash', label: 'Cash received' },\n { value: 'plans', label: 'Plan subscriptions (from wallet)' },\n { value: 'transfers', label: 'Wallet transfers' },\n { value: 'fx', label: 'Currency conversion' },\n { value: 'lending', label: 'Lending' },\n { value: 'other', label: 'Other fees' },\n];\n\n/**\n * Every fee Olive earned, one row each: what it came from, which merchant, what Olive kept and what the processor\n * charged. The filters live in the page address so a view can be shared. Plans paid another way than from a wallet are\n * on the Subscriptions tab.\n */\nexport function EarningsTable({ startDate, endDate }: { startDate?: string; endDate?: string }) {\n const colors = useSourceColors();\n const { values, page, set } = useUrlFilters('er', FILTERS);\n const [search, setSearch] = useState(values.q);\n\n // Typing waits a moment, so a search is one request and not one per key.\n useEffect(() => {\n const timer = setTimeout(() => {\n if (search.trim() !== values.q) set({ q: search.trim() });\n }, 300);\n\n return () => clearTimeout(timer);\n // Only a change the person typed should write to the address.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [search]);\n\n const filters = useMemo(\n () => ({ startDate, endDate, source: values.source, merchantId: values.merchant, q: values.q, page, limit: PAGE_SIZE }),\n [startDate, endDate, values, page],\n );\n const merchants = useMerchantOptions({ startDate, endDate });\n const merchantChoices = [\n { value: '', label: 'Every merchant' },\n ...(merchants.data?.merchants ?? []).map((one) => ({ value: one.id, label: one.name })),\n ];\n const { data, isLoading, isError, refetch } = useEarningsTransactions(filters);\n\n if (isError)\n return (\n <LoadFailed\n message=\"The 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 earned…\" />;\n\n return (\n <div>\n <p className={styles.sectionNote}>\n Every fee Olive earned in this period, and where it came from. Use the filters to narrow it, and download exactly\n what you see.\n </p>\n <div className={styles.toolbar}>\n <Filters label=\"Filter earnings\">\n <FilterSearch\n value={search}\n onChange={setSearch}\n placeholder=\"Search a merchant, what it was for, or a source\"\n label=\"Search earnings\"\n />\n <FilterSelect value={values.source} onChange={(source) => set({ source })} options={SOURCES} label=\"Source\" />\n <FilterSelect\n value={values.merchant}\n onChange={(merchant) => set({ merchant })}\n options={merchantChoices}\n label=\"Merchant\"\n searchable\n />\n </Filters>\n <DownloadButton\n filename={downloadName('olive-earnings', startDate, endDate)}\n load={() => adminEarningsApi.transactionsCsv({ ...filters, page: undefined, limit: undefined })}\n />\n </div>\n\n {data.rows.length === 0 ? (\n <EmptyNote className={table.empty}>No earnings match. Try a wider date range or clear a filter.</EmptyNote>\n ) : (\n <TableShell\n minWidth={960}\n density=\"compact\"\n head={\n <tr>\n <Th>When</Th>\n <Th>Source</Th>\n <Th>Merchant</Th>\n <Th>What it was for</Th>\n <Th align=\"right\">Olive earned</Th>\n <Th align=\"right\">Processor cost</Th>\n <Th align=\"right\">Kept</Th>\n </tr>\n }\n >\n {data.rows.map((row) => (\n <EarningRow key={`${row.id}-${row.currency}`} row={row} color={colors[row.source]} />\n ))}\n </TableShell>\n )}\n\n <Pagination\n page={page}\n totalPages={data.totalPages}\n total={data.total}\n noun={data.total === 1 ? 'earning' : 'earnings'}\n onPage={(next) => set({ page: next })}\n />\n </div>\n );\n}\n\nfunction EarningRow({ row, color }: { row: EarningsRow; color: string }) {\n const money = (minor: string) => formatMinor(minor, row.currency);\n\n return (\n <TableRow>\n <td className={table.num}>\n {new Date(row.at).toLocaleString(undefined, { day: 'numeric', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' })}\n </td>\n <td>\n <span style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}>\n <span aria-hidden style={{ width: 10, height: 10, borderRadius: 3, background: color, flexShrink: 0 }} />\n {row.sourceLabel}\n </span>\n </td>\n <td className={table.name}>\n {row.merchant.id && row.merchant.name ? (\n <Link href={`/admin/tenants/${row.merchant.id}`}>{row.merchant.name}</Link>\n ) : (\n 'No merchant recorded'\n )}\n </td>\n <td>{row.description}</td>\n <td className={table.numStrong}>{money(row.feeMinor)}</td>\n <td className={table.num}>{row.providerMinor === '0' ? '–' : money(row.providerMinor)}</td>\n <td className={table.num}>{money(row.netMinor)}</td>\n </TableRow>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
/* Earnings: a pie with its key, and the ranked list of where fees come from. */
|
|
2
|
-
|
|
3
|
-
.pie {
|
|
4
|
-
display: flex;
|
|
5
|
-
flex-wrap: wrap;
|
|
6
|
-
align-items: center;
|
|
7
|
-
justify-content: center;
|
|
8
|
-
gap: 20px;
|
|
9
|
-
padding: 4px 0;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
.legend {
|
|
13
|
-
display: grid;
|
|
14
|
-
gap: 10px;
|
|
15
|
-
margin: 0;
|
|
16
|
-
padding: 0;
|
|
17
|
-
list-style: none;
|
|
18
|
-
flex: 1 1 200px;
|
|
19
|
-
min-width: 0;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
.legendRow {
|
|
23
|
-
display: grid;
|
|
24
|
-
grid-template-columns: 12px minmax(0, 1fr) auto;
|
|
25
|
-
align-items: center;
|
|
26
|
-
gap: 10px;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
.swatch {
|
|
30
|
-
width: 12px;
|
|
31
|
-
height: 12px;
|
|
32
|
-
border-radius: 4px;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
.legendText {
|
|
36
|
-
display: grid;
|
|
37
|
-
min-width: 0;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
.legendLabel {
|
|
41
|
-
font-size: 13px;
|
|
42
|
-
line-height: 18px;
|
|
43
|
-
color: var(--text-primary);
|
|
44
|
-
overflow: hidden;
|
|
45
|
-
text-overflow: ellipsis;
|
|
46
|
-
white-space: nowrap;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
.legendDetail {
|
|
50
|
-
font-size: 12px;
|
|
51
|
-
line-height: 16px;
|
|
52
|
-
color: var(--text-tertiary);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
.legendNumbers {
|
|
56
|
-
display: grid;
|
|
57
|
-
justify-items: end;
|
|
58
|
-
font-size: 12px;
|
|
59
|
-
line-height: 16px;
|
|
60
|
-
color: var(--text-tertiary);
|
|
61
|
-
font-variant-numeric: tabular-nums;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
.legendNumbers strong {
|
|
65
|
-
font-size: 13px;
|
|
66
|
-
font-weight: 500;
|
|
67
|
-
color: var(--text-primary);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
.rankList {
|
|
71
|
-
display: grid;
|
|
72
|
-
gap: 14px;
|
|
73
|
-
margin: 0;
|
|
74
|
-
padding: 0;
|
|
75
|
-
list-style: none;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
.rankRow {
|
|
79
|
-
display: grid;
|
|
80
|
-
grid-template-columns: minmax(0, 1fr) auto;
|
|
81
|
-
gap: 4px 16px;
|
|
82
|
-
align-items: baseline;
|
|
83
|
-
font-size: 14px;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
.rankName {
|
|
87
|
-
min-width: 0;
|
|
88
|
-
overflow: hidden;
|
|
89
|
-
text-overflow: ellipsis;
|
|
90
|
-
white-space: nowrap;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
.rankSub {
|
|
94
|
-
grid-column: 1;
|
|
95
|
-
font-size: 12px;
|
|
96
|
-
color: var(--text-tertiary);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
.rankAmount {
|
|
100
|
-
grid-row: 1;
|
|
101
|
-
grid-column: 2;
|
|
102
|
-
font-weight: 500;
|
|
103
|
-
font-variant-numeric: tabular-nums;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
.rankBar {
|
|
107
|
-
grid-column: 1 / -1;
|
|
108
|
-
height: 6px;
|
|
109
|
-
border-radius: 999px;
|
|
110
|
-
background: var(--bg-subtle);
|
|
111
|
-
overflow: hidden;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
.rankFill {
|
|
115
|
-
display: block;
|
|
116
|
-
height: 100%;
|
|
117
|
-
border-radius: 999px;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/* Stream against the period before: a faded bar for then, a solid one for now. */
|
|
121
|
-
.cmp {
|
|
122
|
-
display: grid;
|
|
123
|
-
gap: 16px;
|
|
124
|
-
margin: 0;
|
|
125
|
-
padding: 0;
|
|
126
|
-
list-style: none;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
.cmpRow {
|
|
130
|
-
display: grid;
|
|
131
|
-
gap: 4px;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
.cmpHead {
|
|
135
|
-
display: grid;
|
|
136
|
-
grid-template-columns: 12px minmax(0, 1fr) auto;
|
|
137
|
-
align-items: center;
|
|
138
|
-
gap: 10px;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
.cmpDelta {
|
|
142
|
-
font-size: 12px;
|
|
143
|
-
font-weight: 500;
|
|
144
|
-
color: var(--text-tertiary);
|
|
145
|
-
font-variant-numeric: tabular-nums;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
.up {
|
|
149
|
-
color: var(--success);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
.down {
|
|
153
|
-
color: var(--error);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
.cmpTrack {
|
|
157
|
-
display: block;
|
|
158
|
-
height: 8px;
|
|
159
|
-
border-radius: 999px;
|
|
160
|
-
background: var(--bg-subtle);
|
|
161
|
-
overflow: hidden;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
.cmpPrev,
|
|
165
|
-
.cmpNow {
|
|
166
|
-
display: block;
|
|
167
|
-
height: 100%;
|
|
168
|
-
border-radius: 999px;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
.cmpPrev {
|
|
172
|
-
opacity: 0.35;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
.cmpNums {
|
|
176
|
-
display: flex;
|
|
177
|
-
justify-content: space-between;
|
|
178
|
-
gap: 12px;
|
|
179
|
-
font-size: 12px;
|
|
180
|
-
color: var(--text-tertiary);
|
|
181
|
-
font-variant-numeric: tabular-nums;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
.cmpNums strong {
|
|
185
|
-
font-size: 13px;
|
|
186
|
-
font-weight: 500;
|
|
187
|
-
color: var(--text-primary);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/* A metric card that drives the chart: the chosen one is outlined. */
|
|
191
|
-
.metric {
|
|
192
|
-
cursor: pointer;
|
|
193
|
-
border-radius: var(--adm-card-radius);
|
|
194
|
-
outline: 2px solid transparent;
|
|
195
|
-
outline-offset: 2px;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
.metric:focus-visible,
|
|
199
|
-
.metricOn {
|
|
200
|
-
outline-color: var(--text-tertiary);
|
|
201
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { keepPreviousData, useQuery } from "@tanstack/react-query";
|
|
3
|
-
import {
|
|
4
|
-
adminEarningsApi
|
|
5
|
-
} from "@withpotter/lib/admin-earnings-api";
|
|
6
|
-
function useEarnings(range) {
|
|
7
|
-
return useQuery({
|
|
8
|
-
queryKey: ["platform-admin", "earnings", "summary", range],
|
|
9
|
-
queryFn: () => adminEarningsApi.summary(range),
|
|
10
|
-
retry: false,
|
|
11
|
-
staleTime: 6e4
|
|
12
|
-
});
|
|
13
|
-
}
|
|
14
|
-
function useEarningsTransactions(filters) {
|
|
15
|
-
return useQuery({
|
|
16
|
-
queryKey: ["platform-admin", "earnings", "transactions", filters],
|
|
17
|
-
queryFn: () => adminEarningsApi.transactions(filters),
|
|
18
|
-
retry: false,
|
|
19
|
-
staleTime: 3e4,
|
|
20
|
-
placeholderData: keepPreviousData
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
export {
|
|
24
|
-
useEarnings,
|
|
25
|
-
useEarningsTransactions
|
|
26
|
-
};
|
|
27
|
-
//# sourceMappingURL=use-earnings.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/admin/revenue/earnings/use-earnings.ts"],"sourcesContent":["'use client';\n\nimport { keepPreviousData, useQuery } from '@tanstack/react-query';\nimport {\n adminEarningsApi,\n type EarningsFilters,\n} from '@withpotter/lib/admin-earnings-api';\n\n/** What Olive earns in the window, by source, day and merchant. A box without the route answers 404: not available here. */\nexport function useEarnings(range: { startDate?: string; endDate?: string }) {\n return useQuery({\n queryKey: ['platform-admin', 'earnings', 'summary', range],\n queryFn: () => adminEarningsApi.summary(range),\n retry: false,\n staleTime: 60_000,\n });\n}\n\n/** The fee transactions behind the earnings, a page at a time. */\nexport function useEarningsTransactions(filters: EarningsFilters) {\n return useQuery({\n queryKey: ['platform-admin', 'earnings', 'transactions', filters],\n queryFn: () => adminEarningsApi.transactions(filters),\n retry: false,\n staleTime: 30_000,\n placeholderData: keepPreviousData,\n });\n}\n"],"mappings":";AAEA,SAAS,kBAAkB,gBAAgB;AAC3C;AAAA,EACE;AAAA,OAEK;AAGA,SAAS,YAAY,OAAiD;AAC3E,SAAO,SAAS;AAAA,IACd,UAAU,CAAC,kBAAkB,YAAY,WAAW,KAAK;AAAA,IACzD,SAAS,MAAM,iBAAiB,QAAQ,KAAK;AAAA,IAC7C,OAAO;AAAA,IACP,WAAW;AAAA,EACb,CAAC;AACH;AAGO,SAAS,wBAAwB,SAA0B;AAChE,SAAO,SAAS;AAAA,IACd,UAAU,CAAC,kBAAkB,YAAY,gBAAgB,OAAO;AAAA,IAChE,SAAS,MAAM,iBAAiB,aAAa,OAAO;AAAA,IACpD,OAAO;AAAA,IACP,WAAW;AAAA,IACX,iBAAiB;AAAA,EACnB,CAAC;AACH;","names":[]}
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { formatMoney } from "../_ui/money-view";
|
|
2
|
-
function minorToMoney(minor, currency) {
|
|
3
|
-
return formatMoney(Number(minor) / 100, currency, 2);
|
|
4
|
-
}
|
|
5
|
-
function pctChange(now, before) {
|
|
6
|
-
const a = Number(now ?? 0);
|
|
7
|
-
const b = Number(before ?? 0);
|
|
8
|
-
if (b === 0) return null;
|
|
9
|
-
return Number(((a - b) / Math.abs(b) * 100).toFixed(1));
|
|
10
|
-
}
|
|
11
|
-
const METHOD_ORDER = ["card", "ussd", "transfer", "opay"];
|
|
12
|
-
function methodCards(now, before, currency, options = {}) {
|
|
13
|
-
const pick = (rows) => (rows ?? []).filter((r) => r.currency === currency);
|
|
14
|
-
const current = pick(now);
|
|
15
|
-
const total = current.reduce((sum, r) => sum + BigInt(r.amountMinor), BigInt(0));
|
|
16
|
-
const labels = {
|
|
17
|
-
card: "Card",
|
|
18
|
-
ussd: "USSD",
|
|
19
|
-
transfer: "Bank transfer",
|
|
20
|
-
opay: "Opay",
|
|
21
|
-
...options.labels
|
|
22
|
-
};
|
|
23
|
-
const order = options.order ?? METHOD_ORDER;
|
|
24
|
-
const keys = [...order, ...current.map((r) => r.channel.key).filter((k) => !order.includes(k))];
|
|
25
|
-
return [...new Set(keys)].map((key) => {
|
|
26
|
-
const mine = current.find((r) => r.channel.key === key);
|
|
27
|
-
const prior = pick(before).find((r) => r.channel.key === key);
|
|
28
|
-
const amount = BigInt(mine?.amountMinor ?? "0");
|
|
29
|
-
return {
|
|
30
|
-
key,
|
|
31
|
-
label: options.labels?.[key] ?? mine?.channel.label ?? labels[key] ?? key,
|
|
32
|
-
payments: mine?.count ?? 0,
|
|
33
|
-
amountMinor: amount.toString(),
|
|
34
|
-
share: total > BigInt(0) ? Number((Number(amount) / Number(total) * 100).toFixed(1)) : 0,
|
|
35
|
-
change: before ? pctChange(amount.toString(), prior?.amountMinor ?? "0") : null
|
|
36
|
-
};
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
const STATUS_WORDS = {
|
|
40
|
-
success: "Paid",
|
|
41
|
-
pending: "Waiting",
|
|
42
|
-
failed: "Failed",
|
|
43
|
-
refunded: "Refunded",
|
|
44
|
-
cancelled: "Cancelled",
|
|
45
|
-
abandoned: "Abandoned",
|
|
46
|
-
succeeded: "Paid"
|
|
47
|
-
};
|
|
48
|
-
const TYPE_WORDS = {
|
|
49
|
-
order: "Order",
|
|
50
|
-
invoice: "Invoice",
|
|
51
|
-
booking: "Booking",
|
|
52
|
-
membership: "Membership",
|
|
53
|
-
subscription: "Subscription",
|
|
54
|
-
deposit: "Deposit",
|
|
55
|
-
tip: "Tip",
|
|
56
|
-
refund: "Refund",
|
|
57
|
-
payout: "Payout"
|
|
58
|
-
};
|
|
59
|
-
const PURPOSE_WORDS = {
|
|
60
|
-
select_plan: "First payment",
|
|
61
|
-
upgrade: "Upgrade",
|
|
62
|
-
renewal: "Renewal",
|
|
63
|
-
reactivation: "Came back",
|
|
64
|
-
mandate_setup: "Card saved"
|
|
65
|
-
};
|
|
66
|
-
const SUBSCRIPTION_METHOD_WORDS = {
|
|
67
|
-
gateway: "Olive payment gateway",
|
|
68
|
-
wallet: "Box wallet",
|
|
69
|
-
manual: "Recorded by hand"
|
|
70
|
-
};
|
|
71
|
-
const word = (words, key) => words[key] ?? key.replace(/_/g, " ");
|
|
72
|
-
function downloadName(prefix, startDate, endDate) {
|
|
73
|
-
const day = (iso) => iso ? iso.slice(0, 10) : "all";
|
|
74
|
-
return `${prefix}_${day(startDate)}_${day(endDate)}.csv`;
|
|
75
|
-
}
|
|
76
|
-
function plainAmount(minor) {
|
|
77
|
-
const text = String(minor);
|
|
78
|
-
const negative = text.startsWith("-");
|
|
79
|
-
const digits = text.replace("-", "").padStart(3, "0");
|
|
80
|
-
return `${negative ? "-" : ""}${digits.slice(0, -2)}.${digits.slice(-2)}`;
|
|
81
|
-
}
|
|
82
|
-
function planPaymentTotals(byMethod, currency) {
|
|
83
|
-
return byMethod.map((row) => ({
|
|
84
|
-
channel: { key: row.method, label: word(SUBSCRIPTION_METHOD_WORDS, row.method) },
|
|
85
|
-
currency,
|
|
86
|
-
count: row.payments,
|
|
87
|
-
amountMinor: String(Math.round(row.amount * 100))
|
|
88
|
-
}));
|
|
89
|
-
}
|
|
90
|
-
export {
|
|
91
|
-
METHOD_ORDER,
|
|
92
|
-
PURPOSE_WORDS,
|
|
93
|
-
STATUS_WORDS,
|
|
94
|
-
SUBSCRIPTION_METHOD_WORDS,
|
|
95
|
-
TYPE_WORDS,
|
|
96
|
-
downloadName,
|
|
97
|
-
methodCards,
|
|
98
|
-
minorToMoney,
|
|
99
|
-
pctChange,
|
|
100
|
-
plainAmount,
|
|
101
|
-
planPaymentTotals,
|
|
102
|
-
word
|
|
103
|
-
};
|
|
104
|
-
//# sourceMappingURL=ledger-model.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/revenue/ledger-model.ts"],"sourcesContent":["import type { GatewayChannelTotal } from '@withpotter/lib/admin-api';\nimport { formatMoney } from '../_ui/money-view';\n\n/** A kobo amount (a string, so no float is involved on the server) shown as naira. */\nexport function minorToMoney(minor: string | number, currency: string): string {\n return formatMoney(Number(minor) / 100, currency, 2);\n}\n\n/** Percent change from before to now; null when there was nothing before to compare with. */\nexport function pctChange(\n now: string | number | null | undefined,\n before: string | number | null | undefined,\n): number | null {\n const a = Number(now ?? 0);\n const b = Number(before ?? 0);\n if (b === 0) return null;\n\n return Number((((a - b) / Math.abs(b)) * 100).toFixed(1));\n}\n\n/** The four ways a customer can pay at the checkout, in the order the checkout shows them. */\nexport const METHOD_ORDER = ['card', 'ussd', 'transfer', 'opay'] as const;\n\nexport interface MethodCardRow {\n key: string;\n label: string;\n payments: number;\n amountMinor: string;\n /** Share of all money received in this currency, in percent. */\n share: number;\n /** Change in amount since the period before; null when there is no comparison. */\n change: number | null;\n}\n\n/**\n * One card per way of paying, for one currency. The four checkout methods are always there, with zero when nobody\n * used one, so a quiet method is visible rather than missing. Any other way found in the data follows.\n */\nexport function methodCards(\n now: readonly GatewayChannelTotal[],\n before: readonly GatewayChannelTotal[] | null,\n currency: string,\n /** Other ways of paying and their names, for money that does not come through checkout (plan payments). */\n options: { order?: readonly string[]; labels?: Record<string, string> } = {},\n): MethodCardRow[] {\n const pick = (rows: readonly GatewayChannelTotal[] | null) => (rows ?? []).filter((r) => r.currency === currency);\n const current = pick(now);\n const total = current.reduce((sum, r) => sum + BigInt(r.amountMinor), BigInt(0));\n const labels: Record<string, string> = {\n card: 'Card',\n ussd: 'USSD',\n transfer: 'Bank transfer',\n opay: 'Opay',\n ...options.labels,\n };\n const order: readonly string[] = options.order ?? METHOD_ORDER;\n\n const keys = [...order, ...current.map((r) => r.channel.key).filter((k) => !order.includes(k))];\n\n return [...new Set(keys)].map((key) => {\n const mine = current.find((r) => r.channel.key === key);\n const prior = pick(before).find((r) => r.channel.key === key);\n const amount = BigInt(mine?.amountMinor ?? '0');\n\n return {\n key,\n label: options.labels?.[key] ?? mine?.channel.label ?? labels[key] ?? key,\n payments: mine?.count ?? 0,\n amountMinor: amount.toString(),\n share: total > BigInt(0) ? Number(((Number(amount) / Number(total)) * 100).toFixed(1)) : 0,\n change: before ? pctChange(amount.toString(), prior?.amountMinor ?? '0') : null,\n };\n });\n}\n\n/** Words an accountant would use, not the database's. */\nexport const STATUS_WORDS: Record<string, string> = {\n success: 'Paid',\n pending: 'Waiting',\n failed: 'Failed',\n refunded: 'Refunded',\n cancelled: 'Cancelled',\n abandoned: 'Abandoned',\n succeeded: 'Paid',\n};\n\nexport const TYPE_WORDS: Record<string, string> = {\n order: 'Order',\n invoice: 'Invoice',\n booking: 'Booking',\n membership: 'Membership',\n subscription: 'Subscription',\n deposit: 'Deposit',\n tip: 'Tip',\n refund: 'Refund',\n payout: 'Payout',\n};\n\nexport const PURPOSE_WORDS: Record<string, string> = {\n select_plan: 'First payment',\n upgrade: 'Upgrade',\n renewal: 'Renewal',\n reactivation: 'Came back',\n mandate_setup: 'Card saved',\n};\n\nexport const SUBSCRIPTION_METHOD_WORDS: Record<string, string> = {\n gateway: 'Olive payment gateway',\n wallet: 'Box wallet',\n manual: 'Recorded by hand',\n};\n\nexport const word = (words: Record<string, string>, key: string): string => words[key] ?? key.replace(/_/g, ' ');\n\n/** The dates in a download's file name, so a file says what it holds. */\nexport function downloadName(prefix: string, startDate?: string, endDate?: string): string {\n const day = (iso?: string) => (iso ? iso.slice(0, 10) : 'all');\n\n return `${prefix}_${day(startDate)}_${day(endDate)}.csv`;\n}\n\n/** Kobo as plain text a spreadsheet reads (\"1234.50\"), with no currency sign or commas. */\nexport function plainAmount(minor: string | number): string {\n const text = String(minor);\n const negative = text.startsWith('-');\n const digits = text.replace('-', '').padStart(3, '0');\n\n return `${negative ? '-' : ''}${digits.slice(0, -2)}.${digits.slice(-2)}`;\n}\n\n/**\n * The plan payments by way of paying, as the channel totals the method cards read. The api sends naira with kobo\n * (whole kobo after the point); they are turned back into whole kobo here, never rounded to naira.\n */\nexport function planPaymentTotals(\n byMethod: ReadonlyArray<{ method: string; payments: number; amount: number }>,\n currency: string,\n): GatewayChannelTotal[] {\n return byMethod.map((row) => ({\n channel: { key: row.method, label: word(SUBSCRIPTION_METHOD_WORDS, row.method) },\n currency,\n count: row.payments,\n amountMinor: String(Math.round(row.amount * 100)),\n }));\n}\n"],"mappings":"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","names":[]}
|