@withpotter/pages-admin 0.13.0-next.124 → 0.13.0-next.59
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/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 +7 -6
- package/dist/admin/payout-tiers/tiers-table.js.map +1 -1
- package/dist/admin/payout-tiers/tiers-workspace.js +2 -2
- 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
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
dangerRowBtn
|
|
15
15
|
} from "../_ui";
|
|
16
16
|
import { TierDialog } from "./_tier-dialog";
|
|
17
|
-
import { requirementLabel } from "./requirement-labels";
|
|
18
17
|
import { TIERS_KEY, useTiers } from "./use-tiers";
|
|
19
18
|
const TAG = {
|
|
20
19
|
padding: "2px 8px",
|
|
@@ -51,7 +50,7 @@ function TiersTable({
|
|
|
51
50
|
const columns = [
|
|
52
51
|
{
|
|
53
52
|
key: "label",
|
|
54
|
-
header: "
|
|
53
|
+
header: "Tier",
|
|
55
54
|
render: (t) => /* @__PURE__ */ jsxs("span", { children: [
|
|
56
55
|
/* @__PURE__ */ jsx("span", { style: { fontWeight: 500 }, children: t.label }),
|
|
57
56
|
!t.enabled && /* @__PURE__ */ jsx("span", { style: { ...TAG, marginLeft: 8 }, children: "Disabled" }),
|
|
@@ -65,8 +64,10 @@ function TiersTable({
|
|
|
65
64
|
fontFamily: "var(--font-mono, monospace)"
|
|
66
65
|
},
|
|
67
66
|
children: [
|
|
67
|
+
t.key,
|
|
68
|
+
" \xB7 ",
|
|
68
69
|
t.currency,
|
|
69
|
-
" \xB7
|
|
70
|
+
" \xB7 position ",
|
|
70
71
|
t.position
|
|
71
72
|
]
|
|
72
73
|
}
|
|
@@ -75,8 +76,8 @@ function TiersTable({
|
|
|
75
76
|
},
|
|
76
77
|
{
|
|
77
78
|
key: "requirements",
|
|
78
|
-
header: "
|
|
79
|
-
render: (t) => t.requirements.length === 0 ? "\u2013" : /* @__PURE__ */ jsx("span", { style: { display: "flex", flexWrap: "wrap", gap: 4 }, children: t.requirements.map((r) => /* @__PURE__ */ jsx("span", { style: TAG, children:
|
|
79
|
+
header: "Needs",
|
|
80
|
+
render: (t) => t.requirements.length === 0 ? "\u2013" : /* @__PURE__ */ jsx("span", { style: { display: "flex", flexWrap: "wrap", gap: 4 }, children: t.requirements.map((r) => /* @__PURE__ */ jsx("span", { style: TAG, children: r }, r)) })
|
|
80
81
|
},
|
|
81
82
|
{
|
|
82
83
|
key: "transfer",
|
|
@@ -129,7 +130,7 @@ function TiersTable({
|
|
|
129
130
|
isLoading,
|
|
130
131
|
isError,
|
|
131
132
|
emptyTitle: currency && data?.length ? `No ${currency} levels` : "No levels yet",
|
|
132
|
-
emptyDescription: `
|
|
133
|
+
emptyDescription: `With no tier, every business resolves to verification required${currency ? ` in ${currency}` : ""}.`
|
|
133
134
|
}
|
|
134
135
|
),
|
|
135
136
|
editing && /* @__PURE__ */ jsx(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/payout-tiers/tiers-table.tsx"],"sourcesContent":["'use client';\n\nimport { useMutation, useQueryClient } from '@tanstack/react-query';\nimport { Banner } from '@withpotter/ui-kit/banner';\nimport { useConfirm } from '@withpotter/ui-kit/confirm-dialog';\nimport { formatMinor } from '@withpotter/lib/money-minor';\nimport {\n adminMoneyApi,\n type PayoutTier,\n type PayoutTierInput,\n} from '@withpotter/lib/admin-money-api';\nimport {\n DataTable,\n byCurrency,\n rowBtn,\n dangerRowBtn,\n type Column,\n} from '../_ui';\nimport { TierDialog } from './_tier-dialog';\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/payout-tiers/tiers-table.tsx"],"sourcesContent":["'use client';\n\nimport { useMutation, useQueryClient } from '@tanstack/react-query';\nimport { Banner } from '@withpotter/ui-kit/banner';\nimport { useConfirm } from '@withpotter/ui-kit/confirm-dialog';\nimport { formatMinor } from '@withpotter/lib/money-minor';\nimport {\n adminMoneyApi,\n type PayoutTier,\n type PayoutTierInput,\n} from '@withpotter/lib/admin-money-api';\nimport {\n DataTable,\n byCurrency,\n rowBtn,\n dangerRowBtn,\n type Column,\n} from '../_ui';\nimport { TierDialog } from './_tier-dialog';\nimport { TIERS_KEY, useTiers } from './use-tiers';\n\nconst TAG = {\n padding: '2px 8px',\n borderRadius: 999,\n border: '1px solid var(--border-default)',\n fontSize: 12,\n color: 'var(--text-secondary)',\n} as const;\n\ntype Editing = PayoutTier | 'new' | null;\n\n/** The tiers, with edit and remove on each row and the tier dialog. */\nexport function TiersTable({\n editing,\n onEdit,\n currency = '',\n}: {\n editing: Editing;\n onEdit: (next: Editing) => void;\n /** The currency filter's choice; '' lists every currency. */\n currency?: string;\n}) {\n const qc = useQueryClient();\n const confirm = useConfirm();\n const { data, isLoading, isError } = useTiers();\n\n const refresh = () => qc.invalidateQueries({ queryKey: TIERS_KEY });\n const save = useMutation({\n mutationFn: (body: PayoutTierInput) =>\n editing && editing !== 'new'\n ? adminMoneyApi.updateTier(editing.id, body)\n : adminMoneyApi.createTier(body),\n onSuccess: () => {\n close();\n refresh();\n },\n });\n const remove = useMutation({\n mutationFn: (id: string) => adminMoneyApi.removeTier(id),\n onSuccess: refresh,\n });\n // Closing the dialog clears both errors, as opening a fresh one should.\n function close() {\n onEdit(null);\n save.reset();\n remove.reset();\n }\n\n const columns: Column<PayoutTier>[] = [\n {\n key: 'label',\n header: 'Tier',\n render: (t) => (\n <span>\n <span style={{ fontWeight: 500 }}>{t.label}</span>\n {!t.enabled && (\n <span style={{ ...TAG, marginLeft: 8 }}>Disabled</span>\n )}\n <span\n style={{\n display: 'block',\n fontSize: 12,\n color: 'var(--text-tertiary)',\n fontFamily: 'var(--font-mono, monospace)',\n }}\n >\n {t.key} · {t.currency} · position {t.position}\n </span>\n </span>\n ),\n },\n {\n key: 'requirements',\n header: 'Needs',\n render: (t) =>\n t.requirements.length === 0 ? (\n '–'\n ) : (\n <span style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>\n {t.requirements.map((r) => (\n <span key={r} style={TAG}>\n {r}\n </span>\n ))}\n </span>\n ),\n },\n {\n key: 'transfer',\n header: 'Per transfer',\n render: (t) =>\n `${formatMinor(t.minimumMinor, t.currency)} to ${formatMinor(t.maximumMinor, t.currency)}`,\n },\n {\n key: 'daily',\n header: 'Per day',\n render: (t) => formatMinor(t.dailyMaximumMinor, t.currency),\n },\n {\n key: 'monthly',\n header: 'Per month',\n render: (t) =>\n t.monthlyMaximumMinor\n ? formatMinor(t.monthlyMaximumMinor, t.currency)\n : 'No cap',\n },\n {\n key: 'actions',\n header: '',\n render: (t) => (\n <span style={{ display: 'flex', gap: 6 }}>\n <button type=\"button\" style={rowBtn} onClick={() => onEdit(t)}>\n Edit\n </button>\n <button\n type=\"button\"\n style={dangerRowBtn}\n onClick={async () => {\n const ok = await confirm({\n title: `Remove ${t.label}?`,\n description:\n 'Businesses on this tier fall back to the next tier they qualify for.',\n confirmLabel: 'Remove',\n tone: 'danger',\n });\n if (ok) remove.mutate(t.id);\n }}\n >\n Remove\n </button>\n </span>\n ),\n },\n ];\n\n return (\n <>\n {remove.error && !editing && (\n <Banner tone=\"error\">{remove.error.message}</Banner>\n )}\n <DataTable\n columns={columns}\n rows={data && byCurrency(data, currency, (t) => t.currency)}\n getRowKey={(t) => t.id}\n isLoading={isLoading}\n isError={isError}\n emptyTitle={\n currency && data?.length ? `No ${currency} levels` : 'No levels yet'\n }\n emptyDescription={`With no tier, every business resolves to verification required${currency ? ` in ${currency}` : ''}.`}\n />\n {editing && (\n <TierDialog\n tier={editing === 'new' ? null : editing}\n saving={save.isPending}\n error={save.error?.message ?? null}\n onSave={(b) => save.mutate(b)}\n onClose={close}\n />\n )}\n </>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -22,7 +22,7 @@ function TiersWorkspace() {
|
|
|
22
22
|
PageHeader,
|
|
23
23
|
{
|
|
24
24
|
title: "Payout limits",
|
|
25
|
-
description: "
|
|
25
|
+
description: "Per-currency limits by verification level.",
|
|
26
26
|
actions: /* @__PURE__ */ jsxs("div", { className: "adm-toolbar", children: [
|
|
27
27
|
/* @__PURE__ */ jsx(
|
|
28
28
|
CurrencyFilter,
|
|
@@ -44,7 +44,7 @@ function TiersWorkspace() {
|
|
|
44
44
|
open: previewing,
|
|
45
45
|
onClose: () => setPreviewing(false),
|
|
46
46
|
title: "Preview a business",
|
|
47
|
-
subtitle: "Which level
|
|
47
|
+
subtitle: "Which level this business lands on today, and what it is missing.",
|
|
48
48
|
children: /* @__PURE__ */ jsx(TierPreviewTab, {})
|
|
49
49
|
}
|
|
50
50
|
)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/payout-tiers/tiers-workspace.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { Button } from '@withpotter/ui-kit/button';\nimport type { PayoutTier } from '@withpotter/lib/admin-money-api';\nimport {\n CurrencyFilter,\n Drawer,\n PageHeader,\n currenciesIn,\n useCurrencyParam,\n} from '../_ui';\nimport { TierPreviewTab } from './tier-preview-tab';\nimport { TiersTable } from './tiers-table';\nimport { useTiers } from './use-tiers';\n\n/**\n * How much a business may send out, by how far it is verified.\n *\n * This is configuration, not approved policy: nothing is seeded, and with no\n * tier a business resolves to \"verification required\". The preview tab\n * answers the question an admin actually asks: which tier would THIS\n * business get today, and what is it missing?\n */\nexport function TiersWorkspace() {\n const [previewing, setPreviewing] = useState(false);\n const [editing, setEditing] = useState<PayoutTier | 'new' | null>(null);\n const [currency, setCurrency] = useCurrencyParam();\n const tiers = useTiers();\n\n return (\n <>\n <PageHeader\n title=\"Payout limits\"\n description=\"
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/payout-tiers/tiers-workspace.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { Button } from '@withpotter/ui-kit/button';\nimport type { PayoutTier } from '@withpotter/lib/admin-money-api';\nimport {\n CurrencyFilter,\n Drawer,\n PageHeader,\n currenciesIn,\n useCurrencyParam,\n} from '../_ui';\nimport { TierPreviewTab } from './tier-preview-tab';\nimport { TiersTable } from './tiers-table';\nimport { useTiers } from './use-tiers';\n\n/**\n * How much a business may send out, by how far it is verified.\n *\n * This is configuration, not approved policy: nothing is seeded, and with no\n * tier a business resolves to \"verification required\". The preview tab\n * answers the question an admin actually asks: which tier would THIS\n * business get today, and what is it missing?\n */\nexport function TiersWorkspace() {\n const [previewing, setPreviewing] = useState(false);\n const [editing, setEditing] = useState<PayoutTier | 'new' | null>(null);\n const [currency, setCurrency] = useCurrencyParam();\n const tiers = useTiers();\n\n return (\n <>\n <PageHeader\n title=\"Payout limits\"\n description=\"Per-currency limits by verification level.\"\n actions={\n <div className=\"adm-toolbar\">\n <CurrencyFilter\n value={currency}\n onChange={setCurrency}\n currencies={currenciesIn(tiers.data?.map((t) => t.currency))}\n />\n <Button variant=\"secondary\" onClick={() => setPreviewing(true)}>\n Preview a business\n </Button>\n <Button onClick={() => setEditing('new')}>New level</Button>\n </div>\n }\n />\n <TiersTable editing={editing} onEdit={setEditing} currency={currency} />\n <Drawer\n open={previewing}\n onClose={() => setPreviewing(false)}\n title=\"Preview a business\"\n subtitle=\"Which level this business lands on today, and what it is missing.\"\n >\n <TierPreviewTab />\n </Drawer>\n </>\n );\n}\n"],"mappings":";AA+BI,mBAMQ,KADF,YALN;AA7BJ,SAAS,gBAAgB;AACzB,SAAS,cAAc;AAEvB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,sBAAsB;AAC/B,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB;AAUlB,SAAS,iBAAiB;AAC/B,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,KAAK;AAClD,QAAM,CAAC,SAAS,UAAU,IAAI,SAAoC,IAAI;AACtE,QAAM,CAAC,UAAU,WAAW,IAAI,iBAAiB;AACjD,QAAM,QAAQ,SAAS;AAEvB,SACE,iCACE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,aAAY;AAAA,QACZ,SACE,qBAAC,SAAI,WAAU,eACb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,OAAO;AAAA,cACP,UAAU;AAAA,cACV,YAAY,aAAa,MAAM,MAAM,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC;AAAA;AAAA,UAC7D;AAAA,UACA,oBAAC,UAAO,SAAQ,aAAY,SAAS,MAAM,cAAc,IAAI,GAAG,gCAEhE;AAAA,UACA,oBAAC,UAAO,SAAS,MAAM,WAAW,KAAK,GAAG,uBAAS;AAAA,WACrD;AAAA;AAAA,IAEJ;AAAA,IACA,oBAAC,cAAW,SAAkB,QAAQ,YAAY,UAAoB;AAAA,IACtE;AAAA,MAAC;AAAA;AAAA,QACC,MAAM;AAAA,QACN,SAAS,MAAM,cAAc,KAAK;AAAA,QAClC,OAAM;AAAA,QACN,UAAS;AAAA,QAET,8BAAC,kBAAe;AAAA;AAAA,IAClB;AAAA,KACF;AAEJ;","names":[]}
|
|
@@ -16,12 +16,9 @@ import { usePosPeriod } from "../_pos/use-pos-period";
|
|
|
16
16
|
import { usePosSummary } from "../_pos/use-pos";
|
|
17
17
|
import { ShiftsTab } from "./shifts-tab";
|
|
18
18
|
import { TerminalsTab } from "./terminals-tab";
|
|
19
|
-
import { TransactionsTab } from "./transactions-tab";
|
|
20
19
|
const TABS = [
|
|
21
20
|
{ id: "terminals", label: "Terminals" },
|
|
22
|
-
|
|
23
|
-
{ id: "shifts", label: "POS sessions" },
|
|
24
|
-
{ id: "transactions", label: "Transactions" }
|
|
21
|
+
{ id: "shifts", label: "Shifts" }
|
|
25
22
|
];
|
|
26
23
|
const KEYS = TABS.map((t) => t.id);
|
|
27
24
|
function PosWorkspace() {
|
|
@@ -60,8 +57,7 @@ function PosWorkspace() {
|
|
|
60
57
|
/* @__PURE__ */ jsx(PosFigures, { period, phrase, currency }),
|
|
61
58
|
/* @__PURE__ */ jsx(PageTabs, { tabs: TABS, active: tab, onChange: setTab }),
|
|
62
59
|
tab === "terminals" && /* @__PURE__ */ jsx(TerminalsTab, { period, currency }),
|
|
63
|
-
tab === "shifts" && /* @__PURE__ */ jsx(ShiftsTab, { currency })
|
|
64
|
-
tab === "transactions" && /* @__PURE__ */ jsx(TransactionsTab, {})
|
|
60
|
+
tab === "shifts" && /* @__PURE__ */ jsx(ShiftsTab, { currency })
|
|
65
61
|
] });
|
|
66
62
|
}
|
|
67
63
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/pos/pos-workspace.tsx"],"sourcesContent":["'use client';\n\nimport { EmptyNote } from '@withpotter/ui-kit';\nimport {\n AdmLoading,\n CurrencyFilter,\n PageHeader,\n PageTabs,\n currenciesIn,\n useCurrencyParam,\n useTabParam,\n} from '../_ui';\nimport { PosFigures } from '../_pos/pos-figures';\nimport { usePosAccess } from '../_pos/use-pos-access';\nimport { usePosPeriod } from '../_pos/use-pos-period';\nimport { usePosSummary } from '../_pos/use-pos';\nimport { ShiftsTab } from './shifts-tab';\nimport { TerminalsTab } from './terminals-tab';\
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/pos/pos-workspace.tsx"],"sourcesContent":["'use client';\n\nimport { EmptyNote } from '@withpotter/ui-kit';\nimport {\n AdmLoading,\n CurrencyFilter,\n PageHeader,\n PageTabs,\n currenciesIn,\n useCurrencyParam,\n useTabParam,\n} from '../_ui';\nimport { PosFigures } from '../_pos/pos-figures';\nimport { usePosAccess } from '../_pos/use-pos-access';\nimport { usePosPeriod } from '../_pos/use-pos-period';\nimport { usePosSummary } from '../_pos/use-pos';\nimport { ShiftsTab } from './shifts-tab';\nimport { TerminalsTab } from './terminals-tab';\n\n/**\n * Box's POS page has an Orders tab for hardware orders. Olive sells no\n * terminals through the admin, so it has no such tab.\n */\nconst TABS = [\n { id: 'terminals', label: 'Terminals' },\n { id: 'shifts', label: 'Shifts' },\n] as const;\n\nconst KEYS = TABS.map((t) => t.id);\n\n/**\n * POS across every business: the fleet's figures, then every terminal and\n * every till shift. A terminal opens on its own page.\n */\nexport function PosWorkspace() {\n const [tab, setTab] = useTabParam(KEYS);\n const { isLoading, canRead } = usePosAccess();\n const { period, phrase, control } = usePosPeriod();\n const [currency, setCurrency] = useCurrencyParam();\n // The figures' own request (one shared query): its currencies are the\n // filter's choices.\n const summary = usePosSummary(period, canRead);\n const currencies = currenciesIn(\n summary.data?.paymentsAmount.map((a) => a.currency),\n );\n\n if (isLoading) return <AdmLoading label=\"Loading POS…\" />;\n\n if (!canRead)\n return (\n <div>\n <PageHeader title=\"POS\" />\n <EmptyNote well>{\"You don't have permission to see POS.\"}</EmptyNote>\n </div>\n );\n\n return (\n <div>\n <PageHeader\n title=\"POS\"\n actions={\n <div className=\"adm-toolbar\">\n <CurrencyFilter\n value={currency}\n onChange={setCurrency}\n currencies={currencies}\n />\n {control}\n </div>\n }\n />\n <PosFigures period={period} phrase={phrase} currency={currency} />\n <PageTabs tabs={TABS} active={tab} onChange={setTab} />\n {tab === 'terminals' && (\n <TerminalsTab period={period} currency={currency} />\n )}\n {tab === 'shifts' && <ShiftsTab currency={currency} />}\n </div>\n );\n}\n"],"mappings":";AA8CwB,cAIlB,YAJkB;AA5CxB,SAAS,iBAAiB;AAC1B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,kBAAkB;AAC3B,SAAS,oBAAoB;AAC7B,SAAS,oBAAoB;AAC7B,SAAS,qBAAqB;AAC9B,SAAS,iBAAiB;AAC1B,SAAS,oBAAoB;AAM7B,MAAM,OAAO;AAAA,EACX,EAAE,IAAI,aAAa,OAAO,YAAY;AAAA,EACtC,EAAE,IAAI,UAAU,OAAO,SAAS;AAClC;AAEA,MAAM,OAAO,KAAK,IAAI,CAAC,MAAM,EAAE,EAAE;AAM1B,SAAS,eAAe;AAC7B,QAAM,CAAC,KAAK,MAAM,IAAI,YAAY,IAAI;AACtC,QAAM,EAAE,WAAW,QAAQ,IAAI,aAAa;AAC5C,QAAM,EAAE,QAAQ,QAAQ,QAAQ,IAAI,aAAa;AACjD,QAAM,CAAC,UAAU,WAAW,IAAI,iBAAiB;AAGjD,QAAM,UAAU,cAAc,QAAQ,OAAO;AAC7C,QAAM,aAAa;AAAA,IACjB,QAAQ,MAAM,eAAe,IAAI,CAAC,MAAM,EAAE,QAAQ;AAAA,EACpD;AAEA,MAAI,UAAW,QAAO,oBAAC,cAAW,OAAM,qBAAe;AAEvD,MAAI,CAAC;AACH,WACE,qBAAC,SACC;AAAA,0BAAC,cAAW,OAAM,OAAM;AAAA,MACxB,oBAAC,aAAU,MAAI,MAAE,mDAAwC;AAAA,OAC3D;AAGJ,SACE,qBAAC,SACC;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,SACE,qBAAC,SAAI,WAAU,eACb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,OAAO;AAAA,cACP,UAAU;AAAA,cACV;AAAA;AAAA,UACF;AAAA,UACC;AAAA,WACH;AAAA;AAAA,IAEJ;AAAA,IACA,oBAAC,cAAW,QAAgB,QAAgB,UAAoB;AAAA,IAChE,oBAAC,YAAS,MAAM,MAAM,QAAQ,KAAK,UAAU,QAAQ;AAAA,IACpD,QAAQ,eACP,oBAAC,gBAAa,QAAgB,UAAoB;AAAA,IAEnD,QAAQ,YAAY,oBAAC,aAAU,UAAoB;AAAA,KACtD;AAEJ;","names":[]}
|
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from "react";
|
|
4
4
|
import { Button, RemovableChip } from "@withpotter/ui-kit";
|
|
5
|
-
import {
|
|
6
|
-
POS_PAGE_SIZE
|
|
7
|
-
} from "@withpotter/lib/admin-pos-query";
|
|
5
|
+
import { POS_PAGE_SIZE } from "@withpotter/lib/admin-pos-query";
|
|
8
6
|
import { FilterPress, FilterSelect, Filters } from "../_ui";
|
|
9
7
|
import { ListPager } from "../_ui/list-card";
|
|
10
8
|
import { LONG_OPEN_HOURS } from "../_pos/pos-rules";
|
|
@@ -19,13 +17,8 @@ const STATUS_OPTIONS = [
|
|
|
19
17
|
label
|
|
20
18
|
}))
|
|
21
19
|
];
|
|
22
|
-
function ShiftsTab({
|
|
23
|
-
|
|
24
|
-
tenantId: fixedTenantId,
|
|
25
|
-
title = "POS sessions"
|
|
26
|
-
}) {
|
|
27
|
-
const [paramTenantId, setTenantId] = useTenantParam();
|
|
28
|
-
const tenantId = fixedTenantId ?? paramTenantId;
|
|
20
|
+
function ShiftsTab({ currency = "" }) {
|
|
21
|
+
const [tenantId, setTenantId] = useTenantParam();
|
|
29
22
|
const [status, setStatus] = useState("");
|
|
30
23
|
const [longOpen, setLongOpen] = useState(false);
|
|
31
24
|
const [page, setPage] = useState(1);
|
|
@@ -37,7 +30,7 @@ function ShiftsTab({
|
|
|
37
30
|
limit: POS_PAGE_SIZE
|
|
38
31
|
};
|
|
39
32
|
const { data, isLoading, isError, error, refetch } = usePosShifts(filters);
|
|
40
|
-
const narrowed = !!(status || longOpen || tenantId
|
|
33
|
+
const narrowed = !!(status || longOpen || tenantId);
|
|
41
34
|
const reset = (fn) => {
|
|
42
35
|
fn();
|
|
43
36
|
setPage(1);
|
|
@@ -45,18 +38,18 @@ function ShiftsTab({
|
|
|
45
38
|
return /* @__PURE__ */ jsx(
|
|
46
39
|
ShiftsTable,
|
|
47
40
|
{
|
|
48
|
-
title,
|
|
41
|
+
title: "Shifts",
|
|
49
42
|
rows: data?.data,
|
|
50
43
|
total: data?.total,
|
|
51
|
-
withBusiness:
|
|
44
|
+
withBusiness: true,
|
|
52
45
|
currency,
|
|
53
46
|
isLoading,
|
|
54
47
|
isError,
|
|
55
48
|
error,
|
|
56
49
|
onRetry: () => void refetch(),
|
|
57
|
-
emptyTitle: narrowed ? "No
|
|
50
|
+
emptyTitle: narrowed ? "No shifts match." : "No shifts yet.",
|
|
58
51
|
controls: /* @__PURE__ */ jsxs(Filters, { label: "Filter shifts", children: [
|
|
59
|
-
tenantId &&
|
|
52
|
+
tenantId && /* @__PURE__ */ jsx(
|
|
60
53
|
RemovableChip,
|
|
61
54
|
{
|
|
62
55
|
onRemove: () => reset(() => setTenantId("")),
|
|
@@ -93,7 +86,7 @@ function ShiftsTab({
|
|
|
93
86
|
onClick: () => reset(() => {
|
|
94
87
|
setStatus("");
|
|
95
88
|
setLongOpen(false);
|
|
96
|
-
|
|
89
|
+
setTenantId("");
|
|
97
90
|
}),
|
|
98
91
|
children: "Clear filters"
|
|
99
92
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/pos/shifts-tab.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { Button, RemovableChip } from '@withpotter/ui-kit';\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/pos/shifts-tab.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { Button, RemovableChip } from '@withpotter/ui-kit';\nimport { POS_PAGE_SIZE, type ShiftFilters } from '@withpotter/lib/admin-pos-query';\nimport type { PosShiftStatus } from '@withpotter/lib/admin-pos-types';\nimport { FilterPress, FilterSelect, Filters } from '../_ui';\nimport { ListPager } from '../_ui/list-card';\nimport { LONG_OPEN_HOURS } from '../_pos/pos-rules';\nimport { SHIFT_STATUS_LABEL } from '../_pos/pos-words';\nimport { ShiftsTable } from '../_pos/shifts-table';\nimport { usePosShifts } from '../_pos/use-pos';\nimport { useTenantParam } from '../_pos/use-tenant-param';\n\nconst STATUS_OPTIONS = [\n { value: '', label: 'Any status' },\n ...Object.entries(SHIFT_STATUS_LABEL).map(([value, label]) => ({\n value,\n label,\n })),\n];\n\n/** Open and recent shifts across every business, newest first. */\nexport function ShiftsTab({ currency = '' }: { currency?: string }) {\n const [tenantId, setTenantId] = useTenantParam();\n const [status, setStatus] = useState<PosShiftStatus | ''>('');\n const [longOpen, setLongOpen] = useState(false);\n const [page, setPage] = useState(1);\n const filters: ShiftFilters = {\n status: longOpen ? 'open' : status,\n openOverHours: longOpen ? LONG_OPEN_HOURS : undefined,\n tenantId: tenantId || undefined,\n page,\n limit: POS_PAGE_SIZE,\n };\n const { data, isLoading, isError, error, refetch } = usePosShifts(filters);\n const narrowed = !!(status || longOpen || tenantId);\n const reset = (fn: () => void) => {\n fn();\n setPage(1);\n };\n\n return (\n <ShiftsTable\n title=\"Shifts\"\n rows={data?.data}\n total={data?.total}\n withBusiness\n currency={currency}\n isLoading={isLoading}\n isError={isError}\n error={error}\n onRetry={() => void refetch()}\n emptyTitle={narrowed ? 'No shifts match.' : 'No shifts yet.'}\n controls={\n <Filters label=\"Filter shifts\">\n {tenantId && (\n <RemovableChip\n onRemove={() => reset(() => setTenantId(''))}\n removeLabel=\"Show every business\"\n >\n One business\n </RemovableChip>\n )}\n <FilterPress\n on={longOpen}\n onToggle={() => reset(() => setLongOpen((v) => !v))}\n >\n Open over {LONG_OPEN_HOURS} hours\n </FilterPress>\n {!longOpen && (\n <FilterSelect\n label=\"Status\"\n value={status}\n onChange={(v) => reset(() => setStatus(v as PosShiftStatus | ''))}\n options={STATUS_OPTIONS}\n />\n )}\n {narrowed && (\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onClick={() =>\n reset(() => {\n setStatus('');\n setLongOpen(false);\n setTenantId('');\n })\n }\n >\n Clear filters\n </Button>\n )}\n </Filters>\n }\n footer={\n data && data.total > 0 ? (\n <ListPager\n page={page}\n totalPages={Math.max(1, data.totalPages)}\n total={data.total}\n noun={data.total === 1 ? 'shift' : 'shifts'}\n onPage={setPage}\n />\n ) : undefined\n }\n />\n );\n}\n"],"mappings":";AAyDY,cAOF,YAPE;AAvDZ,SAAS,gBAAgB;AACzB,SAAS,QAAQ,qBAAqB;AACtC,SAAS,qBAAwC;AAEjD,SAAS,aAAa,cAAc,eAAe;AACnD,SAAS,iBAAiB;AAC1B,SAAS,uBAAuB;AAChC,SAAS,0BAA0B;AACnC,SAAS,mBAAmB;AAC5B,SAAS,oBAAoB;AAC7B,SAAS,sBAAsB;AAE/B,MAAM,iBAAiB;AAAA,EACrB,EAAE,OAAO,IAAI,OAAO,aAAa;AAAA,EACjC,GAAG,OAAO,QAAQ,kBAAkB,EAAE,IAAI,CAAC,CAAC,OAAO,KAAK,OAAO;AAAA,IAC7D;AAAA,IACA;AAAA,EACF,EAAE;AACJ;AAGO,SAAS,UAAU,EAAE,WAAW,GAAG,GAA0B;AAClE,QAAM,CAAC,UAAU,WAAW,IAAI,eAAe;AAC/C,QAAM,CAAC,QAAQ,SAAS,IAAI,SAA8B,EAAE;AAC5D,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAC9C,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,CAAC;AAClC,QAAM,UAAwB;AAAA,IAC5B,QAAQ,WAAW,SAAS;AAAA,IAC5B,eAAe,WAAW,kBAAkB;AAAA,IAC5C,UAAU,YAAY;AAAA,IACtB;AAAA,IACA,OAAO;AAAA,EACT;AACA,QAAM,EAAE,MAAM,WAAW,SAAS,OAAO,QAAQ,IAAI,aAAa,OAAO;AACzE,QAAM,WAAW,CAAC,EAAE,UAAU,YAAY;AAC1C,QAAM,QAAQ,CAAC,OAAmB;AAChC,OAAG;AACH,YAAQ,CAAC;AAAA,EACX;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAM;AAAA,MACN,MAAM,MAAM;AAAA,MACZ,OAAO,MAAM;AAAA,MACb,cAAY;AAAA,MACZ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,SAAS,MAAM,KAAK,QAAQ;AAAA,MAC5B,YAAY,WAAW,qBAAqB;AAAA,MAC5C,UACE,qBAAC,WAAQ,OAAM,iBACZ;AAAA,oBACC;AAAA,UAAC;AAAA;AAAA,YACC,UAAU,MAAM,MAAM,MAAM,YAAY,EAAE,CAAC;AAAA,YAC3C,aAAY;AAAA,YACb;AAAA;AAAA,QAED;AAAA,QAEF;AAAA,UAAC;AAAA;AAAA,YACC,IAAI;AAAA,YACJ,UAAU,MAAM,MAAM,MAAM,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC;AAAA,YACnD;AAAA;AAAA,cACY;AAAA,cAAgB;AAAA;AAAA;AAAA,QAC7B;AAAA,QACC,CAAC,YACA;AAAA,UAAC;AAAA;AAAA,YACC,OAAM;AAAA,YACN,OAAO;AAAA,YACP,UAAU,CAAC,MAAM,MAAM,MAAM,UAAU,CAAwB,CAAC;AAAA,YAChE,SAAS;AAAA;AAAA,QACX;AAAA,QAED,YACC;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAQ;AAAA,YACR,SAAS,MACP,MAAM,MAAM;AACV,wBAAU,EAAE;AACZ,0BAAY,KAAK;AACjB,0BAAY,EAAE;AAAA,YAChB,CAAC;AAAA,YAEJ;AAAA;AAAA,QAED;AAAA,SAEJ;AAAA,MAEF,QACE,QAAQ,KAAK,QAAQ,IACnB;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA,YAAY,KAAK,IAAI,GAAG,KAAK,UAAU;AAAA,UACvC,OAAO,KAAK;AAAA,UACZ,MAAM,KAAK,UAAU,IAAI,UAAU;AAAA,UACnC,QAAQ;AAAA;AAAA,MACV,IACE;AAAA;AAAA,EAER;AAEJ;","names":[]}
|
|
@@ -17,28 +17,28 @@ function CohortPanel({ cohorts }) {
|
|
|
17
17
|
return /* @__PURE__ */ jsx(
|
|
18
18
|
Panel,
|
|
19
19
|
{
|
|
20
|
-
title: "
|
|
20
|
+
title: "Logo cohort retention",
|
|
21
21
|
action: cohorts.length > 0 && /* @__PURE__ */ jsx(
|
|
22
22
|
ViewSwitch,
|
|
23
23
|
{
|
|
24
24
|
className: "adm-view-switch",
|
|
25
|
-
"aria-label": "Show
|
|
25
|
+
"aria-label": "Show cohort cells as",
|
|
26
26
|
options: LENSES,
|
|
27
27
|
value: lens,
|
|
28
28
|
onChange: setLens
|
|
29
29
|
}
|
|
30
30
|
),
|
|
31
|
-
footer: "
|
|
31
|
+
footer: "Share of each signup-month cohort still transacting (at least 1 paid order) at each month offset. M0 is the signup month itself.",
|
|
32
32
|
flush: true,
|
|
33
|
-
children: cohorts.length === 0 ? /* @__PURE__ */ jsx(EmptyNote, { className: styles.empty, children: "Not enough
|
|
33
|
+
children: cohorts.length === 0 ? /* @__PURE__ */ jsx(EmptyNote, { className: styles.empty, children: "Not enough signup history yet." }) : /* @__PURE__ */ jsx(
|
|
34
34
|
TableShell,
|
|
35
35
|
{
|
|
36
36
|
bare: true,
|
|
37
37
|
minWidth: Math.max(320, 200 + offsets.length * 64),
|
|
38
38
|
density: "compact",
|
|
39
39
|
head: /* @__PURE__ */ jsxs("tr", { children: [
|
|
40
|
-
/* @__PURE__ */ jsx(Th, { children: "
|
|
41
|
-
/* @__PURE__ */ jsx(Th, { align: "right", children: "
|
|
40
|
+
/* @__PURE__ */ jsx(Th, { children: "Cohort" }),
|
|
41
|
+
/* @__PURE__ */ jsx(Th, { align: "right", children: "Tenants" }),
|
|
42
42
|
offsets.map((_, i) => /* @__PURE__ */ jsxs(Th, { align: "right", children: [
|
|
43
43
|
"M",
|
|
44
44
|
i
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/revenue/cohort-panel.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { EmptyNote, TableShell, Th, ViewSwitch } from '@withpotter/ui-kit';\nimport type { PlatformRevenue } from '@withpotter/lib/admin-api';\nimport { TONE } from '../_fx';\nimport { Panel } from '../_ui';\nimport styles from './revenue.module.css';\n\nconst integer = new Intl.NumberFormat('en-US');\n\ntype Cohort = NonNullable<PlatformRevenue['quality']>['cohorts'][number];\ntype Lens = 'share' | 'count';\n\nconst LENSES = [\n { value: 'share' as const, label: 'Share' },\n { value: 'count' as const, label: 'Merchants' },\n];\n\n/**\n * Logo retention by signup month, shaded by the share still transacting.\n * The switch reads each cell as a share of the cohort or as the merchants\n * that share stands for; the shading stays the share either way.\n */\nexport function CohortPanel({ cohorts }: { cohorts: Cohort[] }) {\n const [lens, setLens] = useState<Lens>('share');\n const offsets = cohorts[0]?.retention ?? [];\n const cell = (v: number, size: number) =>\n lens === 'share' ? `${v}%` : integer.format(Math.round((size * v) / 100));\n\n return (\n <Panel\n title=\"
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/revenue/cohort-panel.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { EmptyNote, TableShell, Th, ViewSwitch } from '@withpotter/ui-kit';\nimport type { PlatformRevenue } from '@withpotter/lib/admin-api';\nimport { TONE } from '../_fx';\nimport { Panel } from '../_ui';\nimport styles from './revenue.module.css';\n\nconst integer = new Intl.NumberFormat('en-US');\n\ntype Cohort = NonNullable<PlatformRevenue['quality']>['cohorts'][number];\ntype Lens = 'share' | 'count';\n\nconst LENSES = [\n { value: 'share' as const, label: 'Share' },\n { value: 'count' as const, label: 'Merchants' },\n];\n\n/**\n * Logo retention by signup month, shaded by the share still transacting.\n * The switch reads each cell as a share of the cohort or as the merchants\n * that share stands for; the shading stays the share either way.\n */\nexport function CohortPanel({ cohorts }: { cohorts: Cohort[] }) {\n const [lens, setLens] = useState<Lens>('share');\n const offsets = cohorts[0]?.retention ?? [];\n const cell = (v: number, size: number) =>\n lens === 'share' ? `${v}%` : integer.format(Math.round((size * v) / 100));\n\n return (\n <Panel\n title=\"Logo cohort retention\"\n action={\n cohorts.length > 0 && (\n <ViewSwitch\n className=\"adm-view-switch\"\n aria-label=\"Show cohort cells as\"\n options={LENSES}\n value={lens}\n onChange={setLens}\n />\n )\n }\n footer=\"Share of each signup-month cohort still transacting (at least 1 paid order) at each month offset. M0 is the signup month itself.\"\n flush\n >\n {cohorts.length === 0 ? (\n <EmptyNote className={styles.empty}>\n Not enough signup history yet.\n </EmptyNote>\n ) : (\n <TableShell\n bare\n minWidth={Math.max(320, 200 + offsets.length * 64)}\n density=\"compact\"\n head={\n <tr>\n <Th>Cohort</Th>\n <Th align=\"right\">Tenants</Th>\n {offsets.map((_, i) => (\n <Th key={i} align=\"right\">\n M{i}\n </Th>\n ))}\n </tr>\n }\n >\n {cohorts.map((c) => (\n <tr key={c.cohort}>\n <td className={styles.cohort}>{c.cohort}</td>\n <td className={styles.num}>{integer.format(c.size)}</td>\n {c.retention.map((v, i) => (\n <td\n key={i}\n className={styles.heat}\n data-empty={v == null}\n style={\n v == null\n ? undefined\n : {\n // 60% at most, so dark figures stay readable on the darkest cell.\n background: `color-mix(in srgb, ${TONE.brand.fg} ${Math.round(\n Math.min(v, 100) * 0.6,\n )}%, transparent)`,\n }\n }\n >\n {v == null ? '–' : cell(v, c.size)}\n </td>\n ))}\n </tr>\n ))}\n </TableShell>\n )}\n </Panel>\n );\n}\n"],"mappings":";AAmCU,cA0BM,YA1BN;AAjCV,SAAS,gBAAgB;AACzB,SAAS,WAAW,YAAY,IAAI,kBAAkB;AAEtD,SAAS,YAAY;AACrB,SAAS,aAAa;AACtB,OAAO,YAAY;AAEnB,MAAM,UAAU,IAAI,KAAK,aAAa,OAAO;AAK7C,MAAM,SAAS;AAAA,EACb,EAAE,OAAO,SAAkB,OAAO,QAAQ;AAAA,EAC1C,EAAE,OAAO,SAAkB,OAAO,YAAY;AAChD;AAOO,SAAS,YAAY,EAAE,QAAQ,GAA0B;AAC9D,QAAM,CAAC,MAAM,OAAO,IAAI,SAAe,OAAO;AAC9C,QAAM,UAAU,QAAQ,CAAC,GAAG,aAAa,CAAC;AAC1C,QAAM,OAAO,CAAC,GAAW,SACvB,SAAS,UAAU,GAAG,CAAC,MAAM,QAAQ,OAAO,KAAK,MAAO,OAAO,IAAK,GAAG,CAAC;AAE1E,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAM;AAAA,MACN,QACE,QAAQ,SAAS,KACf;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,cAAW;AAAA,UACX,SAAS;AAAA,UACT,OAAO;AAAA,UACP,UAAU;AAAA;AAAA,MACZ;AAAA,MAGJ,QAAO;AAAA,MACP,OAAK;AAAA,MAEJ,kBAAQ,WAAW,IAClB,oBAAC,aAAU,WAAW,OAAO,OAAO,4CAEpC,IAEA;AAAA,QAAC;AAAA;AAAA,UACC,MAAI;AAAA,UACJ,UAAU,KAAK,IAAI,KAAK,MAAM,QAAQ,SAAS,EAAE;AAAA,UACjD,SAAQ;AAAA,UACR,MACE,qBAAC,QACC;AAAA,gCAAC,MAAG,oBAAM;AAAA,YACV,oBAAC,MAAG,OAAM,SAAQ,qBAAO;AAAA,YACxB,QAAQ,IAAI,CAAC,GAAG,MACf,qBAAC,MAAW,OAAM,SAAQ;AAAA;AAAA,cACtB;AAAA,iBADK,CAET,CACD;AAAA,aACH;AAAA,UAGD,kBAAQ,IAAI,CAAC,MACZ,qBAAC,QACC;AAAA,gCAAC,QAAG,WAAW,OAAO,QAAS,YAAE,QAAO;AAAA,YACxC,oBAAC,QAAG,WAAW,OAAO,KAAM,kBAAQ,OAAO,EAAE,IAAI,GAAE;AAAA,YAClD,EAAE,UAAU,IAAI,CAAC,GAAG,MACnB;AAAA,cAAC;AAAA;AAAA,gBAEC,WAAW,OAAO;AAAA,gBAClB,cAAY,KAAK;AAAA,gBACjB,OACE,KAAK,OACD,SACA;AAAA;AAAA,kBAEE,YAAY,sBAAsB,KAAK,MAAM,EAAE,IAAI,KAAK;AAAA,oBACtD,KAAK,IAAI,GAAG,GAAG,IAAI;AAAA,kBACrB,CAAC;AAAA,gBACH;AAAA,gBAGL,eAAK,OAAO,WAAM,KAAK,GAAG,EAAE,IAAI;AAAA;AAAA,cAd5B;AAAA,YAeP,CACD;AAAA,eArBM,EAAE,MAsBX,CACD;AAAA;AAAA,MACH;AAAA;AAAA,EAEJ;AAEJ;","names":[]}
|
|
@@ -2,57 +2,56 @@ import { jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { AdmLoading, Panel } from "../_ui";
|
|
3
3
|
import { FigureList } from "./figure-list";
|
|
4
4
|
const integer = new Intl.NumberFormat("en-US");
|
|
5
|
-
const pct = (value) => value == null ? "\u2013" : `${value}%`;
|
|
6
5
|
function HealthPanel({
|
|
7
6
|
health,
|
|
8
7
|
money,
|
|
9
8
|
caption
|
|
10
9
|
}) {
|
|
11
10
|
if (!health) {
|
|
12
|
-
return /* @__PURE__ */ jsx(Panel, { title: "
|
|
11
|
+
return /* @__PURE__ */ jsx(Panel, { title: "Revenue health", caption, children: /* @__PURE__ */ jsx(AdmLoading, { label: "Loading revenue health\u2026", size: "sm" }) });
|
|
13
12
|
}
|
|
14
13
|
const rows = [
|
|
15
14
|
{
|
|
16
|
-
label: "
|
|
17
|
-
note: "
|
|
15
|
+
label: "Net new MRR",
|
|
16
|
+
note: "New and expansion, less churned",
|
|
18
17
|
value: money(health.netNewMrr),
|
|
19
|
-
hint: `+${money(health.newMrr)}
|
|
18
|
+
hint: `+${money(health.newMrr)} new \xB7 \u2212${money(health.churnedMrr)} churned`,
|
|
20
19
|
tone: health.netNewMrr < 0 ? "danger" : "default"
|
|
21
20
|
},
|
|
22
21
|
{
|
|
23
|
-
label: "
|
|
24
|
-
note: "Share of
|
|
25
|
-
value:
|
|
22
|
+
label: "Gross revenue retention",
|
|
23
|
+
note: "Share of starting MRR kept, ignoring expansion",
|
|
24
|
+
value: `${health.grossRevenueRetentionPct}%`
|
|
26
25
|
},
|
|
27
26
|
{
|
|
28
|
-
label: "
|
|
29
|
-
note: "
|
|
30
|
-
value:
|
|
27
|
+
label: "Gross MRR churn",
|
|
28
|
+
note: "MRR lost as a share of the base",
|
|
29
|
+
value: `${health.grossMrrChurnPct}%`,
|
|
31
30
|
tone: health.grossMrrChurnPct > 0 ? "danger" : "default"
|
|
32
31
|
},
|
|
33
32
|
{
|
|
34
|
-
label: "
|
|
35
|
-
note: "
|
|
33
|
+
label: "Net new logos",
|
|
34
|
+
note: "Paying merchants gained, less lost",
|
|
36
35
|
value: integer.format(health.netNewLogos),
|
|
37
|
-
hint: `+${integer.format(health.newLogos)}
|
|
36
|
+
hint: `+${integer.format(health.newLogos)} new \xB7 \u2212${integer.format(
|
|
38
37
|
health.churnedLogos
|
|
39
|
-
)}
|
|
38
|
+
)} churned`,
|
|
40
39
|
tone: health.netNewLogos < 0 ? "danger" : "default"
|
|
41
40
|
},
|
|
42
41
|
{
|
|
43
|
-
label: "
|
|
44
|
-
note: "
|
|
45
|
-
value:
|
|
42
|
+
label: "Logo churn",
|
|
43
|
+
note: "Paying merchants lost as a share of the base",
|
|
44
|
+
value: `${health.logoChurnPct}%`,
|
|
46
45
|
tone: health.logoChurnPct > 0 ? "danger" : "default"
|
|
47
46
|
},
|
|
48
47
|
{
|
|
49
|
-
label: "
|
|
50
|
-
note: "Share of
|
|
51
|
-
value:
|
|
48
|
+
label: "GMV concentration",
|
|
49
|
+
note: "Share of GMV from the top 10 merchants",
|
|
50
|
+
value: `${health.gmvConcentrationTop10Pct}%`,
|
|
52
51
|
tone: health.gmvConcentrationTop10Pct > 50 ? "danger" : "default"
|
|
53
52
|
}
|
|
54
53
|
];
|
|
55
|
-
return /* @__PURE__ */ jsx(Panel, { title: "
|
|
54
|
+
return /* @__PURE__ */ jsx(Panel, { title: "Revenue health", caption, children: /* @__PURE__ */ jsx(FigureList, { rows }) });
|
|
56
55
|
}
|
|
57
56
|
export {
|
|
58
57
|
HealthPanel
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/revenue/health-panel.tsx"],"sourcesContent":["import type { PlatformCockpit } from '@withpotter/lib/admin-api';\nimport { AdmLoading, Panel } from '../_ui';\nimport { FigureList, type Figure } from './figure-list';\n\nconst integer = new Intl.NumberFormat('en-US');\n\n/**
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/revenue/health-panel.tsx"],"sourcesContent":["import type { PlatformCockpit } from '@withpotter/lib/admin-api';\nimport { AdmLoading, Panel } from '../_ui';\nimport { FigureList, type Figure } from './figure-list';\n\nconst integer = new Intl.NumberFormat('en-US');\n\n/** MRR movement, logo churn and concentration risk — how healthy the base is. */\nexport function HealthPanel({\n health,\n money,\n caption,\n}: {\n health: PlatformCockpit['health'] | undefined;\n money: (value: number) => string;\n caption: string;\n}) {\n if (!health) {\n return (\n <Panel title=\"Revenue health\" caption={caption}>\n <AdmLoading label=\"Loading revenue health…\" size=\"sm\" />\n </Panel>\n );\n }\n\n const rows: Figure[] = [\n {\n label: 'Net new MRR',\n note: 'New and expansion, less churned',\n value: money(health.netNewMrr),\n hint: `+${money(health.newMrr)} new · −${money(health.churnedMrr)} churned`,\n tone: health.netNewMrr < 0 ? 'danger' : 'default',\n },\n {\n label: 'Gross revenue retention',\n note: 'Share of starting MRR kept, ignoring expansion',\n value: `${health.grossRevenueRetentionPct}%`,\n },\n {\n label: 'Gross MRR churn',\n note: 'MRR lost as a share of the base',\n value: `${health.grossMrrChurnPct}%`,\n tone: health.grossMrrChurnPct > 0 ? 'danger' : 'default',\n },\n {\n label: 'Net new logos',\n note: 'Paying merchants gained, less lost',\n value: integer.format(health.netNewLogos),\n hint: `+${integer.format(health.newLogos)} new · −${integer.format(\n health.churnedLogos,\n )} churned`,\n tone: health.netNewLogos < 0 ? 'danger' : 'default',\n },\n {\n label: 'Logo churn',\n note: 'Paying merchants lost as a share of the base',\n value: `${health.logoChurnPct}%`,\n tone: health.logoChurnPct > 0 ? 'danger' : 'default',\n },\n {\n label: 'GMV concentration',\n note: 'Share of GMV from the top 10 merchants',\n value: `${health.gmvConcentrationTop10Pct}%`,\n tone: health.gmvConcentrationTop10Pct > 50 ? 'danger' : 'default',\n },\n ];\n\n return (\n <Panel title=\"Revenue health\" caption={caption}>\n <FigureList rows={rows} />\n </Panel>\n );\n}\n"],"mappings":"AAmBQ;AAlBR,SAAS,YAAY,aAAa;AAClC,SAAS,kBAA+B;AAExC,MAAM,UAAU,IAAI,KAAK,aAAa,OAAO;AAGtC,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,MAAI,CAAC,QAAQ;AACX,WACE,oBAAC,SAAM,OAAM,kBAAiB,SAC5B,8BAAC,cAAW,OAAM,gCAA0B,MAAK,MAAK,GACxD;AAAA,EAEJ;AAEA,QAAM,OAAiB;AAAA,IACrB;AAAA,MACE,OAAO;AAAA,MACP,MAAM;AAAA,MACN,OAAO,MAAM,OAAO,SAAS;AAAA,MAC7B,MAAM,IAAI,MAAM,OAAO,MAAM,CAAC,mBAAW,MAAM,OAAO,UAAU,CAAC;AAAA,MACjE,MAAM,OAAO,YAAY,IAAI,WAAW;AAAA,IAC1C;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,MAAM;AAAA,MACN,OAAO,GAAG,OAAO,wBAAwB;AAAA,IAC3C;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,MAAM;AAAA,MACN,OAAO,GAAG,OAAO,gBAAgB;AAAA,MACjC,MAAM,OAAO,mBAAmB,IAAI,WAAW;AAAA,IACjD;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,MAAM;AAAA,MACN,OAAO,QAAQ,OAAO,OAAO,WAAW;AAAA,MACxC,MAAM,IAAI,QAAQ,OAAO,OAAO,QAAQ,CAAC,mBAAW,QAAQ;AAAA,QAC1D,OAAO;AAAA,MACT,CAAC;AAAA,MACD,MAAM,OAAO,cAAc,IAAI,WAAW;AAAA,IAC5C;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,MAAM;AAAA,MACN,OAAO,GAAG,OAAO,YAAY;AAAA,MAC7B,MAAM,OAAO,eAAe,IAAI,WAAW;AAAA,IAC7C;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,MAAM;AAAA,MACN,OAAO,GAAG,OAAO,wBAAwB;AAAA,MACzC,MAAM,OAAO,2BAA2B,KAAK,WAAW;AAAA,IAC1D;AAAA,EACF;AAEA,SACE,oBAAC,SAAM,OAAM,kBAAiB,SAC5B,8BAAC,cAAW,MAAY,GAC1B;AAEJ;","names":[]}
|
|
@@ -13,7 +13,7 @@ function PlanMixPanel({
|
|
|
13
13
|
return /* @__PURE__ */ jsx(
|
|
14
14
|
Panel,
|
|
15
15
|
{
|
|
16
|
-
title: "
|
|
16
|
+
title: "Plan mix",
|
|
17
17
|
count: plans.length || void 0,
|
|
18
18
|
caption,
|
|
19
19
|
flush: true,
|
|
@@ -26,8 +26,8 @@ function PlanMixPanel({
|
|
|
26
26
|
head: /* @__PURE__ */ jsxs("tr", { children: [
|
|
27
27
|
/* @__PURE__ */ jsx(Th, { children: "Plan" }),
|
|
28
28
|
/* @__PURE__ */ jsx(Th, { align: "right", children: "Subscribers" }),
|
|
29
|
-
/* @__PURE__ */ jsx(Th, { align: "right", children: "
|
|
30
|
-
/* @__PURE__ */ jsx(Th, { align: "right", children: "
|
|
29
|
+
/* @__PURE__ */ jsx(Th, { align: "right", children: "MRR" }),
|
|
30
|
+
/* @__PURE__ */ jsx(Th, { align: "right", children: "% of MRR" })
|
|
31
31
|
] }),
|
|
32
32
|
children: plans.map((p) => {
|
|
33
33
|
const share = totalMrr > 0 ? p.mrr / totalMrr * 100 : 0;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/revenue/plan-mix-panel.tsx"],"sourcesContent":["import type { PlatformRevenue } from '@withpotter/lib/admin-api';\nimport { EmptyNote, TableRow, TableShell, Th } from '@withpotter/ui-kit';\nimport { Panel } from '../_ui';\nimport styles from './revenue.module.css';\n\nconst integer = new Intl.NumberFormat('en-US');\n\n/** Subscriber count and MRR per plan, ranked by MRR. */\nexport function PlanMixPanel({\n planBreakdown,\n money,\n caption,\n}: {\n planBreakdown: PlatformRevenue['recurring']['planBreakdown'];\n money: (value: number) => string;\n caption: string;\n}) {\n const plans = [...planBreakdown].sort((a, b) => b.mrr - a.mrr);\n const totalMrr = plans.reduce((sum, p) => sum + p.mrr, 0);\n\n return (\n <Panel\n title=\"
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/revenue/plan-mix-panel.tsx"],"sourcesContent":["import type { PlatformRevenue } from '@withpotter/lib/admin-api';\nimport { EmptyNote, TableRow, TableShell, Th } from '@withpotter/ui-kit';\nimport { Panel } from '../_ui';\nimport styles from './revenue.module.css';\n\nconst integer = new Intl.NumberFormat('en-US');\n\n/** Subscriber count and MRR per plan, ranked by MRR. */\nexport function PlanMixPanel({\n planBreakdown,\n money,\n caption,\n}: {\n planBreakdown: PlatformRevenue['recurring']['planBreakdown'];\n money: (value: number) => string;\n caption: string;\n}) {\n const plans = [...planBreakdown].sort((a, b) => b.mrr - a.mrr);\n const totalMrr = plans.reduce((sum, p) => sum + p.mrr, 0);\n\n return (\n <Panel\n title=\"Plan mix\"\n count={plans.length || undefined}\n caption={caption}\n flush\n >\n {plans.length === 0 ? (\n <EmptyNote className={styles.empty}>\n No active plans in this period.\n </EmptyNote>\n ) : (\n <TableShell\n bare\n minWidth={420}\n density=\"compact\"\n head={\n <tr>\n <Th>Plan</Th>\n <Th align=\"right\">Subscribers</Th>\n <Th align=\"right\">MRR</Th>\n <Th align=\"right\">% of MRR</Th>\n </tr>\n }\n >\n {plans.map((p) => {\n const share = totalMrr > 0 ? (p.mrr / totalMrr) * 100 : 0;\n return (\n <TableRow key={p.planId}>\n <td className={styles.name}>\n <span className={styles.tenantText}>\n {p.name}\n <span className={styles.slug}>{p.interval}</span>\n </span>\n </td>\n <td className={styles.num}>\n {integer.format(p.subscribers)}\n </td>\n <td className={styles.numStrong}>{money(p.mrr)}</td>\n <td className={styles.num}>\n <span className={styles.share}>\n <span className={styles.shareTrack} aria-hidden>\n <span\n className={styles.shareFill}\n style={{ width: `${Math.min(share, 100)}%` }}\n />\n </span>\n {share.toFixed(1)}%\n </span>\n </td>\n </TableRow>\n );\n })}\n </TableShell>\n )}\n </Panel>\n );\n}\n"],"mappings":"AA4BQ,cASI,YATJ;AA3BR,SAAS,WAAW,UAAU,YAAY,UAAU;AACpD,SAAS,aAAa;AACtB,OAAO,YAAY;AAEnB,MAAM,UAAU,IAAI,KAAK,aAAa,OAAO;AAGtC,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,QAAM,QAAQ,CAAC,GAAG,aAAa,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,MAAM,EAAE,GAAG;AAC7D,QAAM,WAAW,MAAM,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,KAAK,CAAC;AAExD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAM;AAAA,MACN,OAAO,MAAM,UAAU;AAAA,MACvB;AAAA,MACA,OAAK;AAAA,MAEJ,gBAAM,WAAW,IAChB,oBAAC,aAAU,WAAW,OAAO,OAAO,6CAEpC,IAEA;AAAA,QAAC;AAAA;AAAA,UACC,MAAI;AAAA,UACJ,UAAU;AAAA,UACV,SAAQ;AAAA,UACR,MACE,qBAAC,QACC;AAAA,gCAAC,MAAG,kBAAI;AAAA,YACR,oBAAC,MAAG,OAAM,SAAQ,yBAAW;AAAA,YAC7B,oBAAC,MAAG,OAAM,SAAQ,iBAAG;AAAA,YACrB,oBAAC,MAAG,OAAM,SAAQ,sBAAQ;AAAA,aAC5B;AAAA,UAGD,gBAAM,IAAI,CAAC,MAAM;AAChB,kBAAM,QAAQ,WAAW,IAAK,EAAE,MAAM,WAAY,MAAM;AACxD,mBACE,qBAAC,YACC;AAAA,kCAAC,QAAG,WAAW,OAAO,MACpB,+BAAC,UAAK,WAAW,OAAO,YACrB;AAAA,kBAAE;AAAA,gBACH,oBAAC,UAAK,WAAW,OAAO,MAAO,YAAE,UAAS;AAAA,iBAC5C,GACF;AAAA,cACA,oBAAC,QAAG,WAAW,OAAO,KACnB,kBAAQ,OAAO,EAAE,WAAW,GAC/B;AAAA,cACA,oBAAC,QAAG,WAAW,OAAO,WAAY,gBAAM,EAAE,GAAG,GAAE;AAAA,cAC/C,oBAAC,QAAG,WAAW,OAAO,KACpB,+BAAC,UAAK,WAAW,OAAO,OACtB;AAAA,oCAAC,UAAK,WAAW,OAAO,YAAY,eAAW,MAC7C;AAAA,kBAAC;AAAA;AAAA,oBACC,WAAW,OAAO;AAAA,oBAClB,OAAO,EAAE,OAAO,GAAG,KAAK,IAAI,OAAO,GAAG,CAAC,IAAI;AAAA;AAAA,gBAC7C,GACF;AAAA,gBACC,MAAM,QAAQ,CAAC;AAAA,gBAAE;AAAA,iBACpB,GACF;AAAA,iBArBa,EAAE,MAsBjB;AAAA,UAEJ,CAAC;AAAA;AAAA,MACH;AAAA;AAAA,EAEJ;AAEJ;","names":[]}
|
|
@@ -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":[]}
|