@withpotter/pages-admin 0.13.0-next.123 → 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 -296
- 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
|
@@ -14,56 +14,56 @@ function QualityPanel({
|
|
|
14
14
|
const one = rowFor(gmv, currency || (codes.length === 1 ? codes[0] : null));
|
|
15
15
|
const rows = [
|
|
16
16
|
{
|
|
17
|
-
label: "
|
|
18
|
-
note: "
|
|
17
|
+
label: "Net revenue retention",
|
|
18
|
+
note: "Starting cohort MRR, net of churn",
|
|
19
19
|
value: quality.nrrPct == null ? "\u2013" : `${quality.nrrPct}%`,
|
|
20
|
-
hint: `+${money(quality.expansionMrr)}
|
|
20
|
+
hint: `+${money(quality.expansionMrr)} exp \xB7 \u2212${money(
|
|
21
21
|
quality.contractionMrr
|
|
22
|
-
)}
|
|
22
|
+
)} contr`
|
|
23
23
|
},
|
|
24
24
|
{
|
|
25
|
-
label: "
|
|
26
|
-
note: "
|
|
25
|
+
label: "Quick ratio",
|
|
26
|
+
note: "New + expansion vs churn + contraction",
|
|
27
27
|
value: quality.quickRatio == null ? "\u2013" : `${quality.quickRatio}\xD7`
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
|
-
label: "
|
|
31
|
-
note: "
|
|
30
|
+
label: "Churn rate",
|
|
31
|
+
note: "MRR lost as a share of the base",
|
|
32
32
|
value: quality.mrrChurnRatePct == null ? "\u2013" : `${quality.mrrChurnRatePct}%`,
|
|
33
33
|
tone: quality.mrrChurnRatePct != null && quality.mrrChurnRatePct > 0 ? "danger" : "default"
|
|
34
34
|
},
|
|
35
35
|
{
|
|
36
|
-
label: "
|
|
37
|
-
note: "
|
|
36
|
+
label: "Net GMV",
|
|
37
|
+
note: "Merchant GMV after refunds",
|
|
38
38
|
value: lines((q) => q.netGmv),
|
|
39
39
|
hint: `Gross ${lines((q) => q.grossGmv)}`
|
|
40
40
|
},
|
|
41
41
|
{
|
|
42
|
-
label: "Refunded
|
|
43
|
-
note: "
|
|
42
|
+
label: "Refunded",
|
|
43
|
+
note: "Refunded GMV in the window",
|
|
44
44
|
value: lines((q) => q.refundedGmv)
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
|
-
label: "
|
|
48
|
-
note: "
|
|
47
|
+
label: "Refund rate",
|
|
48
|
+
note: "Refunds as a share of gross GMV",
|
|
49
49
|
value: one ? `${one.refundRatePct}%` : "\u2013",
|
|
50
50
|
hint: one ? one.currency : "Pick a currency"
|
|
51
51
|
},
|
|
52
52
|
{
|
|
53
|
-
label: "
|
|
54
|
-
note: "
|
|
53
|
+
label: "Refunded orders",
|
|
54
|
+
note: "Order count, not GMV",
|
|
55
55
|
value: integer.format(
|
|
56
56
|
(gmv ?? []).filter((q) => !currency || q.currency === currency).reduce((n, q) => n + q.refundedOrders, 0)
|
|
57
57
|
)
|
|
58
58
|
},
|
|
59
59
|
{
|
|
60
|
-
label: "
|
|
61
|
-
note: "
|
|
60
|
+
label: "Logo churn rate",
|
|
61
|
+
note: "Paying merchants lost in the window",
|
|
62
62
|
value: quality.logoChurnRatePct == null ? "\u2013" : `${quality.logoChurnRatePct}%`,
|
|
63
63
|
tone: quality.logoChurnRatePct != null && quality.logoChurnRatePct > 0 ? "danger" : "default"
|
|
64
64
|
}
|
|
65
65
|
];
|
|
66
|
-
return /* @__PURE__ */ jsx(Panel, { title: "
|
|
66
|
+
return /* @__PURE__ */ jsx(Panel, { title: "Revenue quality", caption, children: /* @__PURE__ */ jsx(FigureList, { rows }) });
|
|
67
67
|
}
|
|
68
68
|
export {
|
|
69
69
|
QualityPanel
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/revenue/quality-panel.tsx"],"sourcesContent":["import type { PlatformRevenue } from '@withpotter/lib/admin-api';\nimport { Panel, moneyLines, noneText, rowFor } from '../_ui';\nimport { FigureList, type Figure } from './figure-list';\n\nconst integer = new Intl.NumberFormat('en-US');\n\ntype Quality = NonNullable<PlatformRevenue['quality']>;\n\n/** Retention and refund health over the selected window. */\nexport function QualityPanel({\n quality,\n money,\n currency,\n caption,\n}: {\n quality: Quality;\n /** Formats recurring revenue (MRR), in the currency the API names for it. */\n money: (value: number) => string;\n /** The currency filter's choice; empty shows a line per currency. */\n currency: string;\n caption: string;\n}) {\n // Merchant GMV figures are per currency, joined line by line, never added.\n const gmv = quality.gmvByCurrency;\n const lines = (amount: (q: NonNullable<typeof gmv>[number]) => number) =>\n moneyLines(gmv, currency, amount).join(' · ') || noneText(currency);\n const codes = (gmv ?? []).map((q) => q.currency);\n // A rate only reads within one currency: the chosen one, or the only one.\n const one = rowFor(gmv, currency || (codes.length === 1 ? codes[0] : null));\n const rows: Figure[] = [\n {\n label: '
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/revenue/quality-panel.tsx"],"sourcesContent":["import type { PlatformRevenue } from '@withpotter/lib/admin-api';\nimport { Panel, moneyLines, noneText, rowFor } from '../_ui';\nimport { FigureList, type Figure } from './figure-list';\n\nconst integer = new Intl.NumberFormat('en-US');\n\ntype Quality = NonNullable<PlatformRevenue['quality']>;\n\n/** Retention and refund health over the selected window. */\nexport function QualityPanel({\n quality,\n money,\n currency,\n caption,\n}: {\n quality: Quality;\n /** Formats recurring revenue (MRR), in the currency the API names for it. */\n money: (value: number) => string;\n /** The currency filter's choice; empty shows a line per currency. */\n currency: string;\n caption: string;\n}) {\n // Merchant GMV figures are per currency, joined line by line, never added.\n const gmv = quality.gmvByCurrency;\n const lines = (amount: (q: NonNullable<typeof gmv>[number]) => number) =>\n moneyLines(gmv, currency, amount).join(' · ') || noneText(currency);\n const codes = (gmv ?? []).map((q) => q.currency);\n // A rate only reads within one currency: the chosen one, or the only one.\n const one = rowFor(gmv, currency || (codes.length === 1 ? codes[0] : null));\n const rows: Figure[] = [\n {\n label: 'Net revenue retention',\n note: 'Starting cohort MRR, net of churn',\n value: quality.nrrPct == null ? '–' : `${quality.nrrPct}%`,\n hint: `+${money(quality.expansionMrr)} exp · −${money(\n quality.contractionMrr,\n )} contr`,\n },\n {\n label: 'Quick ratio',\n note: 'New + expansion vs churn + contraction',\n value: quality.quickRatio == null ? '–' : `${quality.quickRatio}×`,\n },\n {\n label: 'Churn rate',\n note: 'MRR lost as a share of the base',\n value:\n quality.mrrChurnRatePct == null ? '–' : `${quality.mrrChurnRatePct}%`,\n tone:\n quality.mrrChurnRatePct != null && quality.mrrChurnRatePct > 0\n ? 'danger'\n : 'default',\n },\n {\n label: 'Net GMV',\n note: 'Merchant GMV after refunds',\n value: lines((q) => q.netGmv),\n hint: `Gross ${lines((q) => q.grossGmv)}`,\n },\n {\n label: 'Refunded',\n note: 'Refunded GMV in the window',\n value: lines((q) => q.refundedGmv),\n },\n {\n label: 'Refund rate',\n note: 'Refunds as a share of gross GMV',\n value: one ? `${one.refundRatePct}%` : '–',\n hint: one ? one.currency : 'Pick a currency',\n },\n {\n label: 'Refunded orders',\n note: 'Order count, not GMV',\n value: integer.format(\n (gmv ?? [])\n .filter((q) => !currency || q.currency === currency)\n .reduce((n, q) => n + q.refundedOrders, 0),\n ),\n },\n {\n label: 'Logo churn rate',\n note: 'Paying merchants lost in the window',\n value:\n quality.logoChurnRatePct == null ? '–' : `${quality.logoChurnRatePct}%`,\n tone:\n quality.logoChurnRatePct != null && quality.logoChurnRatePct > 0\n ? 'danger'\n : 'default',\n },\n ];\n\n return (\n <Panel title=\"Revenue quality\" caption={caption}>\n <FigureList rows={rows} />\n </Panel>\n );\n}\n"],"mappings":"AA6FM;AA5FN,SAAS,OAAO,YAAY,UAAU,cAAc;AACpD,SAAS,kBAA+B;AAExC,MAAM,UAAU,IAAI,KAAK,aAAa,OAAO;AAKtC,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAOG;AAED,QAAM,MAAM,QAAQ;AACpB,QAAM,QAAQ,CAAC,WACb,WAAW,KAAK,UAAU,MAAM,EAAE,KAAK,QAAK,KAAK,SAAS,QAAQ;AACpE,QAAM,SAAS,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,QAAQ;AAE/C,QAAM,MAAM,OAAO,KAAK,aAAa,MAAM,WAAW,IAAI,MAAM,CAAC,IAAI,KAAK;AAC1E,QAAM,OAAiB;AAAA,IACrB;AAAA,MACE,OAAO;AAAA,MACP,MAAM;AAAA,MACN,OAAO,QAAQ,UAAU,OAAO,WAAM,GAAG,QAAQ,MAAM;AAAA,MACvD,MAAM,IAAI,MAAM,QAAQ,YAAY,CAAC,mBAAW;AAAA,QAC9C,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,MAAM;AAAA,MACN,OAAO,QAAQ,cAAc,OAAO,WAAM,GAAG,QAAQ,UAAU;AAAA,IACjE;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,MAAM;AAAA,MACN,OACE,QAAQ,mBAAmB,OAAO,WAAM,GAAG,QAAQ,eAAe;AAAA,MACpE,MACE,QAAQ,mBAAmB,QAAQ,QAAQ,kBAAkB,IACzD,WACA;AAAA,IACR;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,MAAM;AAAA,MACN,OAAO,MAAM,CAAC,MAAM,EAAE,MAAM;AAAA,MAC5B,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC;AAAA,IACzC;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,MAAM;AAAA,MACN,OAAO,MAAM,CAAC,MAAM,EAAE,WAAW;AAAA,IACnC;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,MAAM;AAAA,MACN,OAAO,MAAM,GAAG,IAAI,aAAa,MAAM;AAAA,MACvC,MAAM,MAAM,IAAI,WAAW;AAAA,IAC7B;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,MAAM;AAAA,MACN,OAAO,QAAQ;AAAA,SACZ,OAAO,CAAC,GACN,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,aAAa,QAAQ,EAClD,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,gBAAgB,CAAC;AAAA,MAC7C;AAAA,IACF;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,MAAM;AAAA,MACN,OACE,QAAQ,oBAAoB,OAAO,WAAM,GAAG,QAAQ,gBAAgB;AAAA,MACtE,MACE,QAAQ,oBAAoB,QAAQ,QAAQ,mBAAmB,IAC3D,WACA;AAAA,IACR;AAAA,EACF;AAEA,SACE,oBAAC,SAAM,OAAM,mBAAkB,SAC7B,8BAAC,cAAW,MAAY,GAC1B;AAEJ;","names":[]}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { ADMIN_NAME } from "../_shell/admin-brand";
|
|
3
3
|
import { CountUp, Reveal, TONE } from "../_fx";
|
|
4
4
|
import { MoneyLines, StatCard, rowFor } from "../_ui";
|
|
5
5
|
const integer = new Intl.NumberFormat("en-US");
|
|
6
6
|
function RevenueKpis({
|
|
7
|
-
part,
|
|
8
|
-
extra,
|
|
9
7
|
data,
|
|
10
8
|
money,
|
|
11
9
|
money2,
|
|
@@ -14,116 +12,57 @@ function RevenueKpis({
|
|
|
14
12
|
gmvSpark
|
|
15
13
|
}) {
|
|
16
14
|
const { recurring } = data;
|
|
17
|
-
const tracked = recurring.tracked !== false;
|
|
18
|
-
const notTracked = "Not set up yet";
|
|
19
|
-
const noSource = "The plan payments are not connected yet";
|
|
20
15
|
const gmv = rowFor(data.gmvByCurrency, focus);
|
|
21
16
|
const lifetimeShare = gmv && gmv.lifetime > 0 ? `${Math.round(gmv.inRange / gmv.lifetime * 100)}% of lifetime` : void 0;
|
|
22
|
-
return /* @__PURE__ */ jsxs(
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
value: /* @__PURE__ */ jsx(
|
|
72
|
-
MoneyLines,
|
|
73
|
-
{
|
|
74
|
-
rows: data.gmvByCurrency,
|
|
75
|
-
currency,
|
|
76
|
-
amount: (g) => g.inRange,
|
|
77
|
-
empty: "No sales yet"
|
|
78
|
-
}
|
|
79
|
-
),
|
|
80
|
-
href: "/admin/tenants",
|
|
81
|
-
sub: lifetimeShare,
|
|
82
|
-
hint: `Everything merchants sold through ${ADMIN_NAME}. This is the merchants' money, not ${ADMIN_NAME}'s own income. One line per currency; currencies are never added together. (GMV)`,
|
|
83
|
-
spark: focus ? gmvSpark : void 0,
|
|
84
|
-
sparkColor: TONE.money.fg
|
|
85
|
-
}
|
|
86
|
-
) }),
|
|
87
|
-
extra,
|
|
88
|
-
data.merchantWalletsByCurrency && /* @__PURE__ */ jsx(Reveal, { index: 4, className: "adm-span-3", children: /* @__PURE__ */ jsx(
|
|
89
|
-
StatCard,
|
|
90
|
-
{
|
|
91
|
-
label: "In merchants' wallets",
|
|
92
|
-
value: /* @__PURE__ */ jsx(
|
|
93
|
-
MoneyLines,
|
|
94
|
-
{
|
|
95
|
-
rows: data.merchantWalletsByCurrency,
|
|
96
|
-
currency,
|
|
97
|
-
amount: (r) => r.amount,
|
|
98
|
-
decimals: 2,
|
|
99
|
-
empty: "Nothing held"
|
|
100
|
-
}
|
|
101
|
-
),
|
|
102
|
-
hint: "What merchants hold in their wallets right now: pending, available, held back and on its way to a bank, added together. A balance today, so it does not follow the dates above.",
|
|
103
|
-
sub: "Right now"
|
|
104
|
-
}
|
|
105
|
-
) }),
|
|
106
|
-
data.merchantPayoutsByCurrency && /* @__PURE__ */ jsx(Reveal, { index: 5, className: "adm-span-3", children: /* @__PURE__ */ jsx(
|
|
107
|
-
StatCard,
|
|
108
|
-
{
|
|
109
|
-
label: "Merchants sent out",
|
|
110
|
-
value: /* @__PURE__ */ jsx(
|
|
111
|
-
MoneyLines,
|
|
112
|
-
{
|
|
113
|
-
rows: data.merchantPayoutsByCurrency,
|
|
114
|
-
currency,
|
|
115
|
-
amount: (r) => r.amount,
|
|
116
|
-
decimals: 2,
|
|
117
|
-
empty: "Nothing sent out"
|
|
118
|
-
}
|
|
119
|
-
),
|
|
120
|
-
hint: "What merchants withdrew to their bank accounts in this window. Payouts the bank sent back are taken off."
|
|
121
|
-
}
|
|
122
|
-
) })
|
|
123
|
-
] })
|
|
124
|
-
]
|
|
125
|
-
}
|
|
126
|
-
);
|
|
17
|
+
return /* @__PURE__ */ jsxs("section", { "aria-label": "Revenue indicators", className: "adm-grid", children: [
|
|
18
|
+
/* @__PURE__ */ jsx(Reveal, { index: 0, className: "adm-span-3", children: /* @__PURE__ */ jsx(
|
|
19
|
+
StatCard,
|
|
20
|
+
{
|
|
21
|
+
label: "Monthly recurring revenue (MRR)",
|
|
22
|
+
value: /* @__PURE__ */ jsx(CountUp, { value: recurring.mrr, format: money }),
|
|
23
|
+
hint: `Monthly recurring revenue from active merchant billing subscriptions, in ${data.currency || "USD"}. Yearly plans are divided by 12.`,
|
|
24
|
+
sub: `${integer.format(recurring.activeSubscriptions)} paying`
|
|
25
|
+
}
|
|
26
|
+
) }),
|
|
27
|
+
/* @__PURE__ */ jsx(Reveal, { index: 1, className: "adm-span-3", children: /* @__PURE__ */ jsx(
|
|
28
|
+
StatCard,
|
|
29
|
+
{
|
|
30
|
+
label: "Annual recurring revenue (ARR)",
|
|
31
|
+
value: /* @__PURE__ */ jsx(CountUp, { value: recurring.arr, format: money }),
|
|
32
|
+
hint: "Annual recurring revenue: what the monthly recurring revenue adds up to over a year if nothing changes (MRR \xD7 12).",
|
|
33
|
+
sub: "MRR \xD7 12"
|
|
34
|
+
}
|
|
35
|
+
) }),
|
|
36
|
+
/* @__PURE__ */ jsx(Reveal, { index: 2, className: "adm-span-3", children: /* @__PURE__ */ jsx(
|
|
37
|
+
StatCard,
|
|
38
|
+
{
|
|
39
|
+
label: "Average revenue per account (ARPA)",
|
|
40
|
+
value: /* @__PURE__ */ jsx(CountUp, { value: recurring.arpa, format: money2 }),
|
|
41
|
+
hint: "What the average paying merchant pays each month: monthly recurring revenue divided by the number of paying merchants.",
|
|
42
|
+
sub: "Per paying account"
|
|
43
|
+
}
|
|
44
|
+
) }),
|
|
45
|
+
/* @__PURE__ */ jsx(Reveal, { index: 3, className: "adm-span-3", children: /* @__PURE__ */ jsx(
|
|
46
|
+
StatCard,
|
|
47
|
+
{
|
|
48
|
+
label: "Merchant sales (GMV)",
|
|
49
|
+
value: /* @__PURE__ */ jsx(
|
|
50
|
+
MoneyLines,
|
|
51
|
+
{
|
|
52
|
+
rows: data.gmvByCurrency,
|
|
53
|
+
currency,
|
|
54
|
+
amount: (g) => g.inRange,
|
|
55
|
+
empty: "No sales yet"
|
|
56
|
+
}
|
|
57
|
+
),
|
|
58
|
+
href: "/admin/tenants",
|
|
59
|
+
sub: lifetimeShare,
|
|
60
|
+
hint: `The merchant sales ${ADMIN_NAME}'s recurring revenue sits on, not ${ADMIN_NAME}'s own income (that's MRR). One line per currency; currencies are never added together or converted.`,
|
|
61
|
+
spark: focus ? gmvSpark : void 0,
|
|
62
|
+
sparkColor: TONE.money.fg
|
|
63
|
+
}
|
|
64
|
+
) })
|
|
65
|
+
] });
|
|
127
66
|
}
|
|
128
67
|
export {
|
|
129
68
|
RevenueKpis
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/revenue/revenue-kpis.tsx"],"sourcesContent":["import { ADMIN_NAME } from '../_shell/admin-brand';\nimport type {
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/revenue/revenue-kpis.tsx"],"sourcesContent":["import { ADMIN_NAME } from '../_shell/admin-brand';\nimport type { PlatformRevenue } from '@withpotter/lib/admin-api';\nimport { CountUp, Reveal, TONE } from '../_fx';\nimport { MoneyLines, StatCard, rowFor } from '../_ui';\n\nconst integer = new Intl.NumberFormat('en-US');\n\n/** The four headline figures: recurring revenue first, then merchant GMV. */\nexport function RevenueKpis({\n data,\n money,\n money2,\n currency,\n focus,\n gmvSpark,\n}: {\n data: PlatformRevenue;\n /** Formats recurring revenue, in the currency the API names for it. */\n money: (value: number) => string;\n money2: (value: number) => string;\n /** The currency filter's choice; empty shows a line per currency. */\n currency: string;\n /** The one currency the share and sparkline follow, or null. */\n focus: string | null;\n gmvSpark: number[];\n}) {\n const { recurring } = data;\n const gmv = rowFor(data.gmvByCurrency, focus);\n const lifetimeShare =\n gmv && gmv.lifetime > 0\n ? `${Math.round((gmv.inRange / gmv.lifetime) * 100)}% of lifetime`\n : undefined;\n\n return (\n <section aria-label=\"Revenue indicators\" className=\"adm-grid\">\n <Reveal index={0} className=\"adm-span-3\">\n <StatCard\n label=\"Monthly recurring revenue (MRR)\"\n value={<CountUp value={recurring.mrr} format={money} />}\n hint={`Monthly recurring revenue from active merchant billing subscriptions, in ${data.currency || 'USD'}. Yearly plans are divided by 12.`}\n sub={`${integer.format(recurring.activeSubscriptions)} paying`}\n />\n </Reveal>\n <Reveal index={1} className=\"adm-span-3\">\n <StatCard\n label=\"Annual recurring revenue (ARR)\"\n value={<CountUp value={recurring.arr} format={money} />}\n hint=\"Annual recurring revenue: what the monthly recurring revenue adds up to over a year if nothing changes (MRR × 12).\"\n sub=\"MRR × 12\"\n />\n </Reveal>\n <Reveal index={2} className=\"adm-span-3\">\n <StatCard\n label=\"Average revenue per account (ARPA)\"\n value={<CountUp value={recurring.arpa} format={money2} />}\n hint=\"What the average paying merchant pays each month: monthly recurring revenue divided by the number of paying merchants.\"\n sub=\"Per paying account\"\n />\n </Reveal>\n <Reveal index={3} className=\"adm-span-3\">\n <StatCard\n label=\"Merchant sales (GMV)\"\n value={\n <MoneyLines\n rows={data.gmvByCurrency}\n currency={currency}\n amount={(g) => g.inRange}\n empty=\"No sales yet\"\n />\n }\n href=\"/admin/tenants\"\n sub={lifetimeShare}\n hint={`The merchant sales ${ADMIN_NAME}'s recurring revenue sits on, not ${ADMIN_NAME}'s own income (that's MRR). One line per currency; currencies are never added together or converted.`}\n spark={focus ? gmvSpark : undefined}\n sparkColor={TONE.money.fg}\n />\n </Reveal>\n </section>\n );\n}\n"],"mappings":"AAkCI,SAIa,KAJb;AAlCJ,SAAS,kBAAkB;AAE3B,SAAS,SAAS,QAAQ,YAAY;AACtC,SAAS,YAAY,UAAU,cAAc;AAE7C,MAAM,UAAU,IAAI,KAAK,aAAa,OAAO;AAGtC,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAUG;AACD,QAAM,EAAE,UAAU,IAAI;AACtB,QAAM,MAAM,OAAO,KAAK,eAAe,KAAK;AAC5C,QAAM,gBACJ,OAAO,IAAI,WAAW,IAClB,GAAG,KAAK,MAAO,IAAI,UAAU,IAAI,WAAY,GAAG,CAAC,kBACjD;AAEN,SACE,qBAAC,aAAQ,cAAW,sBAAqB,WAAU,YACjD;AAAA,wBAAC,UAAO,OAAO,GAAG,WAAU,cAC1B;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,OAAO,oBAAC,WAAQ,OAAO,UAAU,KAAK,QAAQ,OAAO;AAAA,QACrD,MAAM,4EAA4E,KAAK,YAAY,KAAK;AAAA,QACxG,KAAK,GAAG,QAAQ,OAAO,UAAU,mBAAmB,CAAC;AAAA;AAAA,IACvD,GACF;AAAA,IACA,oBAAC,UAAO,OAAO,GAAG,WAAU,cAC1B;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,OAAO,oBAAC,WAAQ,OAAO,UAAU,KAAK,QAAQ,OAAO;AAAA,QACrD,MAAK;AAAA,QACL,KAAI;AAAA;AAAA,IACN,GACF;AAAA,IACA,oBAAC,UAAO,OAAO,GAAG,WAAU,cAC1B;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,OAAO,oBAAC,WAAQ,OAAO,UAAU,MAAM,QAAQ,QAAQ;AAAA,QACvD,MAAK;AAAA,QACL,KAAI;AAAA;AAAA,IACN,GACF;AAAA,IACA,oBAAC,UAAO,OAAO,GAAG,WAAU,cAC1B;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,OACE;AAAA,UAAC;AAAA;AAAA,YACC,MAAM,KAAK;AAAA,YACX;AAAA,YACA,QAAQ,CAAC,MAAM,EAAE;AAAA,YACjB,OAAM;AAAA;AAAA,QACR;AAAA,QAEF,MAAK;AAAA,QACL,KAAK;AAAA,QACL,MAAM,sBAAsB,UAAU,qCAAqC,UAAU;AAAA,QACrF,OAAO,QAAQ,WAAW;AAAA,QAC1B,YAAY,KAAK,MAAM;AAAA;AAAA,IACzB,GACF;AAAA,KACF;AAEJ;","names":[]}
|
|
@@ -1,65 +1,63 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import { TimeSeriesChart } from "@withpotter/components/charts";
|
|
5
|
+
import { PICK_CURRENCY, Panel, moneyIn, rowFor, toTrend } from "../_ui";
|
|
6
|
+
import { Tinted } from "../audit/tinted";
|
|
5
7
|
import { HealthPanel } from "./health-panel";
|
|
6
8
|
import { PlanMixPanel } from "./plan-mix-panel";
|
|
7
|
-
import { planLines } from "./plan-lines";
|
|
8
|
-
import { PlansPanel } from "./plans-panel";
|
|
9
9
|
import { RevenueKpis } from "./revenue-kpis";
|
|
10
|
-
import
|
|
10
|
+
import { SubscriptionsPanel } from "./subscriptions-panel";
|
|
11
|
+
import { TopTenantsPanel } from "./top-tenants-panel";
|
|
12
|
+
import styles from "./revenue.module.css";
|
|
11
13
|
function RevenueOverview({
|
|
12
|
-
startDate,
|
|
13
|
-
endDate,
|
|
14
14
|
data,
|
|
15
15
|
health,
|
|
16
16
|
money,
|
|
17
17
|
money2,
|
|
18
18
|
currency,
|
|
19
19
|
focus,
|
|
20
|
+
period,
|
|
20
21
|
span,
|
|
21
|
-
recurringCaption
|
|
22
|
-
plans
|
|
22
|
+
recurringCaption
|
|
23
23
|
}) {
|
|
24
|
-
const
|
|
24
|
+
const gmv = rowFor(data.gmvByCurrency, focus);
|
|
25
|
+
const gmvSeries = useMemo(() => toTrend(gmv?.series, "gmv"), [gmv]);
|
|
26
|
+
const gmvCaption = focus ? `${period} \xB7 ${focus}` : period;
|
|
25
27
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
26
|
-
/* @__PURE__ */ jsx(
|
|
27
|
-
EarningsSummary,
|
|
28
|
-
{
|
|
29
|
-
startDate,
|
|
30
|
-
endDate,
|
|
31
|
-
currency,
|
|
32
|
-
recurring: data.recurring,
|
|
33
|
-
span
|
|
34
|
-
}
|
|
35
|
-
),
|
|
36
|
-
/* @__PURE__ */ jsx("h2", { className: ledgerStyles.sectionTitle, children: "Plan income" }),
|
|
37
|
-
/* @__PURE__ */ jsxs("p", { className: ledgerStyles.sectionNote, children: [
|
|
38
|
-
"What merchants pay Olive for their plans, each month and over a year.",
|
|
39
|
-
recurringTracked ? "" : " Plan income is not set up on this box yet."
|
|
40
|
-
] }),
|
|
41
28
|
/* @__PURE__ */ jsx(
|
|
42
29
|
RevenueKpis,
|
|
43
30
|
{
|
|
44
|
-
part: "olive",
|
|
45
31
|
data,
|
|
46
32
|
money,
|
|
47
33
|
money2,
|
|
48
34
|
currency,
|
|
49
35
|
focus,
|
|
50
|
-
gmvSpark:
|
|
36
|
+
gmvSpark: gmvSeries.map((p) => p.value)
|
|
51
37
|
}
|
|
52
38
|
),
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
39
|
+
/* @__PURE__ */ jsx("div", { className: "adm-grid", children: /* @__PURE__ */ jsx("div", { className: "adm-span-12", children: /* @__PURE__ */ jsx(
|
|
40
|
+
Panel,
|
|
41
|
+
{
|
|
42
|
+
title: "GMV trend",
|
|
43
|
+
caption: gmvCaption,
|
|
44
|
+
height: "calc(var(--adm-panel-h) + 32px)",
|
|
45
|
+
children: focus ? /* @__PURE__ */ jsx("div", { className: styles.flatChart, children: /* @__PURE__ */ jsx(Tinted, { tone: "money", children: /* @__PURE__ */ jsx(
|
|
46
|
+
TimeSeriesChart,
|
|
47
|
+
{
|
|
48
|
+
data: gmvSeries,
|
|
49
|
+
compare: null,
|
|
50
|
+
compareLabel: `Previous ${span}`,
|
|
51
|
+
formatValue: moneyIn(focus),
|
|
52
|
+
emptyMessage: `No paid ${focus} orders in this window yet`,
|
|
53
|
+
height: 240
|
|
54
|
+
}
|
|
55
|
+
) }) }) : /* @__PURE__ */ jsx("p", { className: styles.empty, children: PICK_CURRENCY })
|
|
56
|
+
}
|
|
57
|
+
) }) }),
|
|
58
|
+
/* @__PURE__ */ jsxs("div", { className: "adm-grid", children: [
|
|
59
|
+
/* @__PURE__ */ jsx("div", { className: "adm-span-7", children: /* @__PURE__ */ jsx(HealthPanel, { health, money, caption: recurringCaption }) }),
|
|
60
|
+
/* @__PURE__ */ jsx("div", { className: "adm-span-5", children: /* @__PURE__ */ jsx(
|
|
63
61
|
PlanMixPanel,
|
|
64
62
|
{
|
|
65
63
|
planBreakdown: data.recurring.planBreakdown,
|
|
@@ -68,8 +66,17 @@ function RevenueOverview({
|
|
|
68
66
|
}
|
|
69
67
|
) })
|
|
70
68
|
] }),
|
|
71
|
-
/* @__PURE__ */
|
|
72
|
-
|
|
69
|
+
/* @__PURE__ */ jsxs("div", { className: "adm-grid", children: [
|
|
70
|
+
/* @__PURE__ */ jsx("div", { className: "adm-span-8", children: /* @__PURE__ */ jsx(
|
|
71
|
+
TopTenantsPanel,
|
|
72
|
+
{
|
|
73
|
+
tenants: gmv?.topTenants ?? [],
|
|
74
|
+
lifetime: gmv?.lifetime ?? 0,
|
|
75
|
+
focus
|
|
76
|
+
}
|
|
77
|
+
) }),
|
|
78
|
+
/* @__PURE__ */ jsx("div", { className: "adm-span-4", children: /* @__PURE__ */ jsx(SubscriptionsPanel, { recurring: data.recurring, span }) })
|
|
79
|
+
] })
|
|
73
80
|
] });
|
|
74
81
|
}
|
|
75
82
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/revenue/revenue-overview.tsx"],"sourcesContent":["'use client';\n\nimport
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/revenue/revenue-overview.tsx"],"sourcesContent":["'use client';\n\nimport { useMemo } from 'react';\nimport { TimeSeriesChart } from '@withpotter/components/charts';\nimport type { PlatformCockpit, PlatformRevenue } from '@withpotter/lib/admin-api';\nimport { PICK_CURRENCY, Panel, moneyIn, rowFor, toTrend } from '../_ui';\nimport { Tinted } from '../audit/tinted';\nimport { HealthPanel } from './health-panel';\nimport { PlanMixPanel } from './plan-mix-panel';\nimport { RevenueKpis } from './revenue-kpis';\nimport { SubscriptionsPanel } from './subscriptions-panel';\nimport { TopTenantsPanel } from './top-tenants-panel';\nimport styles from './revenue.module.css';\n\n/**\n * Overview tab: the four headline figures, then the GMV trend as the one\n * full-width primary chart, then revenue health, plan mix, top merchants\n * and subscriptions beneath it. Merchant GMV is per currency: the chart and\n * the leaderboard follow one currency (`focus`), never a mix.\n */\nexport function RevenueOverview({\n data,\n health,\n money,\n money2,\n currency,\n focus,\n period,\n span,\n recurringCaption,\n}: {\n data: PlatformRevenue;\n health: PlatformCockpit['health'] | undefined;\n /** Formats recurring revenue, in the currency the API names for it. */\n money: (value: number) => string;\n money2: (value: number) => string;\n /** The currency filter's choice; empty shows a line per currency. */\n currency: string;\n /** The one currency the chart and leaderboard show, or null. */\n focus: string | null;\n period: string;\n span: string;\n recurringCaption: string;\n}) {\n const gmv = rowFor(data.gmvByCurrency, focus);\n const gmvSeries = useMemo(() => toTrend(gmv?.series, 'gmv'), [gmv]);\n const gmvCaption = focus ? `${period} · ${focus}` : period;\n\n return (\n <>\n <RevenueKpis\n data={data}\n money={money}\n money2={money2}\n currency={currency}\n focus={focus}\n gmvSpark={gmvSeries.map((p) => p.value)}\n />\n\n <div className=\"adm-grid\">\n <div className=\"adm-span-12\">\n <Panel\n title=\"GMV trend\"\n caption={gmvCaption}\n height=\"calc(var(--adm-panel-h) + 32px)\"\n >\n {focus ? (\n <div className={styles.flatChart}>\n <Tinted tone=\"money\">\n <TimeSeriesChart\n data={gmvSeries}\n compare={null}\n compareLabel={`Previous ${span}`}\n formatValue={moneyIn(focus)}\n emptyMessage={`No paid ${focus} orders in this window yet`}\n height={240}\n />\n </Tinted>\n </div>\n ) : (\n <p className={styles.empty}>{PICK_CURRENCY}</p>\n )}\n </Panel>\n </div>\n </div>\n\n <div className=\"adm-grid\">\n <div className=\"adm-span-7\">\n <HealthPanel health={health} money={money} caption={recurringCaption} />\n </div>\n <div className=\"adm-span-5\">\n <PlanMixPanel\n planBreakdown={data.recurring.planBreakdown}\n money={money}\n caption={recurringCaption}\n />\n </div>\n </div>\n\n <div className=\"adm-grid\">\n <div className=\"adm-span-8\">\n <TopTenantsPanel\n tenants={gmv?.topTenants ?? []}\n lifetime={gmv?.lifetime ?? 0}\n focus={focus}\n />\n </div>\n <div className=\"adm-span-4\">\n <SubscriptionsPanel recurring={data.recurring} span={span} />\n </div>\n </div>\n </>\n );\n}\n"],"mappings":";AAiDI,mBACE,KAoCA,YArCF;AA/CJ,SAAS,eAAe;AACxB,SAAS,uBAAuB;AAEhC,SAAS,eAAe,OAAO,SAAS,QAAQ,eAAe;AAC/D,SAAS,cAAc;AACvB,SAAS,mBAAmB;AAC5B,SAAS,oBAAoB;AAC7B,SAAS,mBAAmB;AAC5B,SAAS,0BAA0B;AACnC,SAAS,uBAAuB;AAChC,OAAO,YAAY;AAQZ,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAaG;AACD,QAAM,MAAM,OAAO,KAAK,eAAe,KAAK;AAC5C,QAAM,YAAY,QAAQ,MAAM,QAAQ,KAAK,QAAQ,KAAK,GAAG,CAAC,GAAG,CAAC;AAClE,QAAM,aAAa,QAAQ,GAAG,MAAM,SAAM,KAAK,KAAK;AAEpD,SACE,iCACE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,UAAU,UAAU,IAAI,CAAC,MAAM,EAAE,KAAK;AAAA;AAAA,IACxC;AAAA,IAEA,oBAAC,SAAI,WAAU,YACb,8BAAC,SAAI,WAAU,eACb;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,SAAS;AAAA,QACT,QAAO;AAAA,QAEN,kBACC,oBAAC,SAAI,WAAW,OAAO,WACrB,8BAAC,UAAO,MAAK,SACX;AAAA,UAAC;AAAA;AAAA,YACC,MAAM;AAAA,YACN,SAAS;AAAA,YACT,cAAc,YAAY,IAAI;AAAA,YAC9B,aAAa,QAAQ,KAAK;AAAA,YAC1B,cAAc,WAAW,KAAK;AAAA,YAC9B,QAAQ;AAAA;AAAA,QACV,GACF,GACF,IAEA,oBAAC,OAAE,WAAW,OAAO,OAAQ,yBAAc;AAAA;AAAA,IAE/C,GACF,GACF;AAAA,IAEA,qBAAC,SAAI,WAAU,YACb;AAAA,0BAAC,SAAI,WAAU,cACb,8BAAC,eAAY,QAAgB,OAAc,SAAS,kBAAkB,GACxE;AAAA,MACA,oBAAC,SAAI,WAAU,cACb;AAAA,QAAC;AAAA;AAAA,UACC,eAAe,KAAK,UAAU;AAAA,UAC9B;AAAA,UACA,SAAS;AAAA;AAAA,MACX,GACF;AAAA,OACF;AAAA,IAEA,qBAAC,SAAI,WAAU,YACb;AAAA,0BAAC,SAAI,WAAU,cACb;AAAA,QAAC;AAAA;AAAA,UACC,SAAS,KAAK,cAAc,CAAC;AAAA,UAC7B,UAAU,KAAK,YAAY;AAAA,UAC3B;AAAA;AAAA,MACF,GACF;AAAA,MACA,oBAAC,SAAI,WAAU,cACb,8BAAC,sBAAmB,WAAW,KAAK,WAAW,MAAY,GAC7D;AAAA,OACF;AAAA,KACF;AAEJ;","names":[]}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { EmptyBlock } from "../audit/empty-block";
|
|
4
|
-
import ledgerStyles from "./ledger.module.css";
|
|
5
4
|
import { CohortPanel } from "./cohort-panel";
|
|
6
5
|
import { QualityPanel } from "./quality-panel";
|
|
7
6
|
function RevenueRetention({
|
|
@@ -11,22 +10,19 @@ function RevenueRetention({
|
|
|
11
10
|
caption
|
|
12
11
|
}) {
|
|
13
12
|
if (!quality) {
|
|
14
|
-
return /* @__PURE__ */ jsx(EmptyBlock, { message: "Retention
|
|
13
|
+
return /* @__PURE__ */ jsx(EmptyBlock, { message: "Retention appears once there is enough billing history." });
|
|
15
14
|
}
|
|
16
|
-
return /* @__PURE__ */ jsxs(
|
|
17
|
-
/* @__PURE__ */ jsx("
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
) }),
|
|
28
|
-
/* @__PURE__ */ jsx("div", { className: "adm-span-7", children: /* @__PURE__ */ jsx(CohortPanel, { cohorts: quality.cohorts }) })
|
|
29
|
-
] })
|
|
15
|
+
return /* @__PURE__ */ jsxs("div", { className: "adm-grid", children: [
|
|
16
|
+
/* @__PURE__ */ jsx("div", { className: "adm-span-5", children: /* @__PURE__ */ jsx(
|
|
17
|
+
QualityPanel,
|
|
18
|
+
{
|
|
19
|
+
quality,
|
|
20
|
+
money,
|
|
21
|
+
currency,
|
|
22
|
+
caption
|
|
23
|
+
}
|
|
24
|
+
) }),
|
|
25
|
+
/* @__PURE__ */ jsx("div", { className: "adm-span-7", children: /* @__PURE__ */ jsx(CohortPanel, { cohorts: quality.cohorts }) })
|
|
30
26
|
] });
|
|
31
27
|
}
|
|
32
28
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/revenue/revenue-retention.tsx"],"sourcesContent":["'use client';\n\nimport type { PlatformRevenue } from '@withpotter/lib/admin-api';\nimport { EmptyBlock } from '../audit/empty-block';\nimport
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/revenue/revenue-retention.tsx"],"sourcesContent":["'use client';\n\nimport type { PlatformRevenue } from '@withpotter/lib/admin-api';\nimport { EmptyBlock } from '../audit/empty-block';\nimport { CohortPanel } from './cohort-panel';\nimport { QualityPanel } from './quality-panel';\n\n/** Retention tab: revenue quality beside logo cohort retention. */\nexport function RevenueRetention({\n quality,\n money,\n currency,\n caption,\n}: {\n quality: PlatformRevenue['quality'];\n money: (value: number) => string;\n /** The currency filter's choice; empty shows a line per currency. */\n currency: string;\n caption: string;\n}) {\n if (!quality) {\n return (\n <EmptyBlock message=\"Retention appears once there is enough billing history.\" />\n );\n }\n\n return (\n <div className=\"adm-grid\">\n <div className=\"adm-span-5\">\n <QualityPanel\n quality={quality}\n money={money}\n currency={currency}\n caption={caption}\n />\n </div>\n <div className=\"adm-span-7\">\n <CohortPanel cohorts={quality.cohorts} />\n </div>\n </div>\n );\n}\n"],"mappings":";AAsBM,cAKF,YALE;AAnBN,SAAS,kBAAkB;AAC3B,SAAS,mBAAmB;AAC5B,SAAS,oBAAoB;AAGtB,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAMG;AACD,MAAI,CAAC,SAAS;AACZ,WACE,oBAAC,cAAW,SAAQ,2DAA0D;AAAA,EAElF;AAEA,SACE,qBAAC,SAAI,WAAU,YACb;AAAA,wBAAC,SAAI,WAAU,cACb;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA;AAAA,IACF,GACF;AAAA,IACA,oBAAC,SAAI,WAAU,cACb,8BAAC,eAAY,SAAS,QAAQ,SAAS,GACzC;AAAA,KACF;AAEJ;","names":[]}
|