@withpotter/pages-admin 0.13.0-next.124 → 0.13.0-next.58
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/_money/cash-postings.js +3 -20
- package/dist/admin/_money/cash-postings.js.map +1 -1
- package/dist/admin/_money/cash.module.css +0 -10
- package/dist/admin/_money/finder-format.js +0 -1
- package/dist/admin/_money/finder-format.js.map +1 -1
- package/dist/admin/_money/format.js +0 -1
- package/dist/admin/_money/format.js.map +1 -1
- package/dist/admin/_overview/activation-panel.js +6 -6
- package/dist/admin/_overview/activation-panel.js.map +1 -1
- package/dist/admin/_overview/attention-groups.js +8 -8
- package/dist/admin/_overview/attention-groups.js.map +1 -1
- package/dist/admin/_overview/attention-panel.js +33 -37
- package/dist/admin/_overview/attention-panel.js.map +1 -1
- package/dist/admin/_overview/attention.module.css +3 -0
- package/dist/admin/_overview/control-room.js +13 -41
- package/dist/admin/_overview/control-room.js.map +1 -1
- package/dist/admin/_overview/kpi-row.js +7 -7
- package/dist/admin/_overview/kpi-row.js.map +1 -1
- package/dist/admin/_overview/leaders-panel.js +1 -1
- package/dist/admin/_overview/leaders-panel.js.map +1 -1
- package/dist/admin/_overview/overdue-outstanding.js +1 -2
- package/dist/admin/_overview/overdue-outstanding.js.map +1 -1
- package/dist/admin/_overview/portfolio-panel.js +5 -5
- package/dist/admin/_overview/portfolio-panel.js.map +1 -1
- package/dist/admin/_overview/position-panel.js +12 -8
- package/dist/admin/_overview/position-panel.js.map +1 -1
- package/dist/admin/_pos/pos-figures.js +5 -18
- package/dist/admin/_pos/pos-figures.js.map +1 -1
- package/dist/admin/_pos/pos-rules.js +0 -2
- package/dist/admin/_pos/pos-rules.js.map +1 -1
- package/dist/admin/_pos/pos-words.js +1 -6
- package/dist/admin/_pos/pos-words.js.map +1 -1
- package/dist/admin/_pos/use-pos.js +1 -70
- package/dist/admin/_pos/use-pos.js.map +1 -1
- package/dist/admin/_shell/nav-config.js +3 -3
- package/dist/admin/_shell/nav-config.js.map +1 -1
- package/dist/admin/_shell/nav-filter.js +2 -4
- package/dist/admin/_shell/nav-filter.js.map +1 -1
- package/dist/admin/_shell/nav-row.js +2 -5
- package/dist/admin/_shell/nav-row.js.map +1 -1
- package/dist/admin/_shell/sidebar-footer.js +0 -20
- package/dist/admin/_shell/sidebar-footer.js.map +1 -1
- package/dist/admin/_shell/sidebar-footer.module.css +0 -17
- package/dist/admin/_shell/sidebar.js +15 -25
- package/dist/admin/_shell/sidebar.js.map +1 -1
- package/dist/admin/_shell/sidebar.module.css +0 -69
- package/dist/admin/_shell/use-fluid-hover.js +6 -37
- package/dist/admin/_shell/use-fluid-hover.js.map +1 -1
- package/dist/admin/_ui/admin.css +0 -7
- package/dist/admin/_ui/data-table.js +1 -1
- package/dist/admin/_ui/data-table.js.map +1 -1
- package/dist/admin/_ui/list-card.js +1 -1
- package/dist/admin/_ui/list-card.js.map +1 -1
- package/dist/admin/_ui/list-card.module.css +16 -2
- package/dist/admin/_ui/money-view.js +0 -1
- package/dist/admin/_ui/money-view.js.map +1 -1
- package/dist/admin/_ui/page-header.js +4 -31
- package/dist/admin/_ui/page-header.js.map +1 -1
- package/dist/admin/_ui/panel.module.css +10 -0
- package/dist/admin/_ui/stat-card.js +2 -11
- package/dist/admin/_ui/stat-card.js.map +1 -1
- package/dist/admin/_ui/trend.js +2 -2
- package/dist/admin/_ui/trend.js.map +1 -1
- package/dist/admin/_ui/workspace-tab.js +1 -11
- package/dist/admin/_ui/workspace-tab.js.map +1 -1
- package/dist/admin/accounts/account-panel.js +6 -49
- package/dist/admin/accounts/account-panel.js.map +1 -1
- package/dist/admin/accounts/account-panel.module.css +0 -73
- package/dist/admin/accounts/account-row.js +2 -11
- package/dist/admin/accounts/account-row.js.map +1 -1
- package/dist/admin/accounts/account-vitals.js +13 -15
- package/dist/admin/accounts/account-vitals.js.map +1 -1
- package/dist/admin/accounts/accounts-table.js +8 -39
- package/dist/admin/accounts/accounts-table.js.map +1 -1
- package/dist/admin/accounts/accounts-workspace.js +2 -2
- package/dist/admin/accounts/accounts-workspace.js.map +1 -1
- package/dist/admin/accounts/owner-session.js +6 -0
- package/dist/admin/accounts/owner-session.js.map +1 -1
- package/dist/admin/accounts/rows.module.css +0 -17
- package/dist/admin/audit/status-pill.js +0 -6
- package/dist/admin/audit/status-pill.js.map +1 -1
- package/dist/admin/audit/status-pill.module.css +0 -4
- package/dist/admin/commerce/commerce-overview.js +2 -2
- package/dist/admin/commerce/commerce-overview.js.map +1 -1
- package/dist/admin/commerce/merchant-meta.js +0 -1
- package/dist/admin/commerce/merchant-meta.js.map +1 -1
- package/dist/admin/commerce/merchant-view.js +2 -2
- package/dist/admin/commerce/merchant-view.js.map +1 -1
- package/dist/admin/feedback/prompt-operations.module.css +2 -0
- package/dist/admin/feedback/prompt-preview.module.css +2 -0
- package/dist/admin/hooks.js +1 -57
- package/dist/admin/hooks.js.map +1 -1
- package/dist/admin/layout.js +2 -8
- package/dist/admin/layout.js.map +1 -1
- package/dist/admin/page.js +8 -28
- package/dist/admin/page.js.map +1 -1
- package/dist/admin/payout-tiers/_tier-dialog.js +21 -31
- package/dist/admin/payout-tiers/_tier-dialog.js.map +1 -1
- package/dist/admin/payout-tiers/tier-preview-result.js +9 -10
- package/dist/admin/payout-tiers/tier-preview-result.js.map +1 -1
- package/dist/admin/payout-tiers/tiers-table.js +17 -40
- package/dist/admin/payout-tiers/tiers-table.js.map +1 -1
- package/dist/admin/payout-tiers/tiers-workspace.js +17 -20
- package/dist/admin/payout-tiers/tiers-workspace.js.map +1 -1
- package/dist/admin/pos/pos-workspace.js +2 -6
- package/dist/admin/pos/pos-workspace.js.map +1 -1
- package/dist/admin/pos/shifts-tab.js +9 -16
- package/dist/admin/pos/shifts-tab.js.map +1 -1
- package/dist/admin/revenue/cohort-panel.js +6 -6
- package/dist/admin/revenue/cohort-panel.js.map +1 -1
- package/dist/admin/revenue/health-panel.js +21 -22
- package/dist/admin/revenue/health-panel.js.map +1 -1
- package/dist/admin/revenue/plan-mix-panel.js +3 -3
- package/dist/admin/revenue/plan-mix-panel.js.map +1 -1
- package/dist/admin/revenue/quality-panel.js +19 -19
- package/dist/admin/revenue/quality-panel.js.map +1 -1
- package/dist/admin/revenue/revenue-kpis.js +50 -111
- package/dist/admin/revenue/revenue-kpis.js.map +1 -1
- package/dist/admin/revenue/revenue-overview.js +46 -39
- package/dist/admin/revenue/revenue-overview.js.map +1 -1
- package/dist/admin/revenue/revenue-retention.js +13 -17
- package/dist/admin/revenue/revenue-retention.js.map +1 -1
- package/dist/admin/revenue/revenue-workspace.js +6 -53
- package/dist/admin/revenue/revenue-workspace.js.map +1 -1
- package/dist/admin/revenue/subscriptions-panel.js +14 -14
- package/dist/admin/revenue/subscriptions-panel.js.map +1 -1
- package/dist/admin/revenue/top-tenants-panel.js +4 -4
- package/dist/admin/revenue/top-tenants-panel.js.map +1 -1
- package/dist/admin/settings/account-tab.js +25 -1
- package/dist/admin/settings/account-tab.js.map +1 -1
- package/dist/admin/settings/general-tab.js +2 -2
- package/dist/admin/settings/general-tab.js.map +1 -1
- package/dist/admin/settings/system-status-card.js +30 -0
- package/dist/admin/settings/system-status-card.js.map +1 -0
- package/dist/admin/support/queue-card.module.css +4 -1
- package/dist/admin/tenants/[id]/commerce/format.js +1 -2
- package/dist/admin/tenants/[id]/commerce/format.js.map +1 -1
- package/dist/admin/tenants/[id]/commerce/recent-orders.js +1 -1
- package/dist/admin/tenants/[id]/commerce/recent-orders.js.map +1 -1
- package/dist/admin/tenants/[id]/page.js +10 -37
- package/dist/admin/tenants/[id]/page.js.map +1 -1
- package/dist/admin/tenants/[id]/tenant-layout.module.css +4 -1
- package/dist/admin/tenants/[id]/tenant-overview.js +2 -3
- package/dist/admin/tenants/[id]/tenant-overview.js.map +1 -1
- package/dist/admin/tenants/[id]/tenant-overview.module.css +1 -1
- package/dist/admin/tenants/[id]/tenant-pos.js +16 -64
- package/dist/admin/tenants/[id]/tenant-pos.js.map +1 -1
- package/dist/admin/tenants/[id]/tenant-wallet-card.js +2 -3
- package/dist/admin/tenants/[id]/tenant-wallet-card.js.map +1 -1
- package/dist/admin/tenants/[id]/verification/document-viewer.js +52 -3
- package/dist/admin/tenants/[id]/verification/document-viewer.js.map +1 -1
- package/dist/admin/tenants/[id]/verification/document-viewer.module.css +0 -51
- package/dist/admin/tenants/[id]/verification/tenant-verification.js +0 -10
- package/dist/admin/tenants/[id]/verification/tenant-verification.js.map +1 -1
- package/dist/admin/tenants/tenant-list-row.js +1 -2
- package/dist/admin/tenants/tenant-list-row.js.map +1 -1
- package/dist/admin/tenants/tenants-table.js +2 -3
- package/dist/admin/tenants/tenants-table.js.map +1 -1
- package/dist/admin/tenants/tenants-workspace.js +2 -2
- package/dist/admin/tenants/tenants-workspace.js.map +1 -1
- package/dist/admin/verification/verification-page.js +3 -19
- package/dist/admin/verification/verification-page.js.map +1 -1
- package/dist/admin/verification/verification.module.css +0 -10
- package/dist/admin/verification/waiting-case-actions.js +68 -0
- package/dist/admin/verification/waiting-case-actions.js.map +1 -0
- package/dist/admin/verification/waiting-cases.js +48 -66
- package/dist/admin/verification/waiting-cases.js.map +1 -1
- package/package.json +5 -5
- package/dist/admin/_pos/olive-balance.js +0 -40
- package/dist/admin/_pos/olive-balance.js.map +0 -1
- package/dist/admin/_pos/olive-state.js +0 -23
- package/dist/admin/_pos/olive-state.js.map +0 -1
- package/dist/admin/_pos/payments-model.js +0 -55
- package/dist/admin/_pos/payments-model.js.map +0 -1
- package/dist/admin/_pos/tenant-operators.js +0 -63
- package/dist/admin/_pos/tenant-operators.js.map +0 -1
- package/dist/admin/_pos/tenant-outlets.js +0 -77
- package/dist/admin/_pos/tenant-outlets.js.map +0 -1
- package/dist/admin/_pos/tenant-pos-figures.js +0 -82
- package/dist/admin/_pos/tenant-pos-figures.js.map +0 -1
- package/dist/admin/_pos/tenant-requests.js +0 -64
- package/dist/admin/_pos/tenant-requests.js.map +0 -1
- package/dist/admin/_pos/tenant-transactions.js +0 -145
- package/dist/admin/_pos/tenant-transactions.js.map +0 -1
- package/dist/admin/_shell/nav-fold.js +0 -14
- package/dist/admin/_shell/nav-fold.js.map +0 -1
- package/dist/admin/_shell/nav-group.js +0 -78
- package/dist/admin/_shell/nav-group.js.map +0 -1
- package/dist/admin/_shell/use-open-groups.js +0 -64
- package/dist/admin/_shell/use-open-groups.js.map +0 -1
- package/dist/admin/_ui/page-header.module.css +0 -45
- package/dist/admin/accounts/account-sessions.js +0 -70
- package/dist/admin/accounts/account-sessions.js.map +0 -1
- package/dist/admin/accounts/account-sessions.module.css +0 -55
- package/dist/admin/accounts/account-stage-filter.js +0 -29
- package/dist/admin/accounts/account-stage-filter.js.map +0 -1
- package/dist/admin/accounts/account-stage.js +0 -81
- package/dist/admin/accounts/account-stage.js.map +0 -1
- package/dist/admin/accounts/hint.js +0 -12
- package/dist/admin/accounts/hint.js.map +0 -1
- package/dist/admin/payout-tiers/requirement-labels.js +0 -70
- package/dist/admin/payout-tiers/requirement-labels.js.map +0 -1
- package/dist/admin/pos/transactions-tab.js +0 -214
- package/dist/admin/pos/transactions-tab.js.map +0 -1
- package/dist/admin/revenue/channel-panel.js +0 -49
- package/dist/admin/revenue/channel-panel.js.map +0 -1
- package/dist/admin/revenue/channel-rows.js +0 -17
- package/dist/admin/revenue/channel-rows.js.map +0 -1
- package/dist/admin/revenue/download-button.js +0 -40
- package/dist/admin/revenue/download-button.js.map +0 -1
- package/dist/admin/revenue/earnings/earnings-colors.js +0 -25
- package/dist/admin/revenue/earnings/earnings-colors.js.map +0 -1
- package/dist/admin/revenue/earnings/earnings-legend.js +0 -75
- package/dist/admin/revenue/earnings/earnings-legend.js.map +0 -1
- package/dist/admin/revenue/earnings/earnings-summary.js +0 -308
- package/dist/admin/revenue/earnings/earnings-summary.js.map +0 -1
- package/dist/admin/revenue/earnings/earnings-table.js +0 -143
- package/dist/admin/revenue/earnings/earnings-table.js.map +0 -1
- package/dist/admin/revenue/earnings/earnings.module.css +0 -201
- package/dist/admin/revenue/earnings/use-earnings.js +0 -27
- package/dist/admin/revenue/earnings/use-earnings.js.map +0 -1
- package/dist/admin/revenue/ledger-model.js +0 -104
- package/dist/admin/revenue/ledger-model.js.map +0 -1
- package/dist/admin/revenue/ledger.module.css +0 -144
- package/dist/admin/revenue/merchant-headline.js +0 -37
- package/dist/admin/revenue/merchant-headline.js.map +0 -1
- package/dist/admin/revenue/merchant-payments-view.js +0 -234
- package/dist/admin/revenue/merchant-payments-view.js.map +0 -1
- package/dist/admin/revenue/method-cards.js +0 -40
- package/dist/admin/revenue/method-cards.js.map +0 -1
- package/dist/admin/revenue/payment-drawer.js +0 -53
- package/dist/admin/revenue/payment-drawer.js.map +0 -1
- package/dist/admin/revenue/plan-lines.js +0 -36
- package/dist/admin/revenue/plan-lines.js.map +0 -1
- package/dist/admin/revenue/plan-payment-drawer.js +0 -42
- package/dist/admin/revenue/plan-payment-drawer.js.map +0 -1
- package/dist/admin/revenue/plans-panel.js +0 -48
- package/dist/admin/revenue/plans-panel.js.map +0 -1
- package/dist/admin/revenue/platform-fees.js +0 -9
- package/dist/admin/revenue/platform-fees.js.map +0 -1
- package/dist/admin/revenue/print-summary-button.js +0 -27
- package/dist/admin/revenue/print-summary-button.js.map +0 -1
- package/dist/admin/revenue/subscriptions-view.js +0 -213
- package/dist/admin/revenue/subscriptions-view.js.map +0 -1
- package/dist/admin/revenue/summary-download.js +0 -38
- package/dist/admin/revenue/summary-download.js.map +0 -1
- package/dist/admin/revenue/summary-print.js +0 -82
- package/dist/admin/revenue/summary-print.js.map +0 -1
- package/dist/admin/revenue/summary-rows.js +0 -44
- package/dist/admin/revenue/summary-rows.js.map +0 -1
- package/dist/admin/revenue/url-filters.js +0 -36
- package/dist/admin/revenue/url-filters.js.map +0 -1
- package/dist/admin/revenue/use-url-filters.js +0 -24
- package/dist/admin/revenue/use-url-filters.js.map +0 -1
- package/dist/admin/tenants/[id]/use-tenant-wallet.js +0 -23
- package/dist/admin/tenants/[id]/use-tenant-wallet.js.map +0 -1
- package/dist/admin/tenants/[id]/verification/document-file.js +0 -78
- package/dist/admin/tenants/[id]/verification/document-file.js.map +0 -1
- package/dist/admin/tenants/[id]/verification/sent-files.js +0 -33
- package/dist/admin/tenants/[id]/verification/sent-files.js.map +0 -1
- package/dist/admin/verification/preview-dialog.js +0 -111
- package/dist/admin/verification/preview-dialog.js.map +0 -1
|
@@ -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",
|
|
@@ -49,49 +48,27 @@ function TiersTable({
|
|
|
49
48
|
remove.reset();
|
|
50
49
|
}
|
|
51
50
|
const columns = [
|
|
51
|
+
{ key: "label", header: "Tier", render: (t) => `${t.label} (${t.key})` },
|
|
52
|
+
{ key: "currency", header: "Currency", render: (t) => t.currency },
|
|
53
|
+
{ key: "position", header: "Position", render: (t) => t.position },
|
|
52
54
|
{
|
|
53
|
-
key: "
|
|
54
|
-
header: "
|
|
55
|
-
render: (t) =>
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
{
|
|
61
|
-
style: {
|
|
62
|
-
display: "block",
|
|
63
|
-
fontSize: 12,
|
|
64
|
-
color: "var(--text-tertiary)",
|
|
65
|
-
fontFamily: "var(--font-mono, monospace)"
|
|
66
|
-
},
|
|
67
|
-
children: [
|
|
68
|
-
t.currency,
|
|
69
|
-
" \xB7 priority ",
|
|
70
|
-
t.position
|
|
71
|
-
]
|
|
72
|
-
}
|
|
73
|
-
)
|
|
74
|
-
] })
|
|
55
|
+
key: "limits",
|
|
56
|
+
header: "Per transfer \xB7 daily \xB7 monthly",
|
|
57
|
+
render: (t) => [
|
|
58
|
+
`${formatMinor(t.minimumMinor, t.currency)}\u2013${formatMinor(t.maximumMinor, t.currency)}`,
|
|
59
|
+
formatMinor(t.dailyMaximumMinor, t.currency),
|
|
60
|
+
t.monthlyMaximumMinor ? formatMinor(t.monthlyMaximumMinor, t.currency) : "no cap"
|
|
61
|
+
].join(" \xB7 ")
|
|
75
62
|
},
|
|
76
63
|
{
|
|
77
64
|
key: "requirements",
|
|
78
|
-
header: "
|
|
79
|
-
render: (t) => t.requirements.length === 0 ? "
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
key: "transfer",
|
|
83
|
-
header: "Per transfer",
|
|
84
|
-
render: (t) => `${formatMinor(t.minimumMinor, t.currency)} to ${formatMinor(t.maximumMinor, t.currency)}`
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
key: "daily",
|
|
88
|
-
header: "Per day",
|
|
89
|
-
render: (t) => formatMinor(t.dailyMaximumMinor, t.currency)
|
|
65
|
+
header: "Requires",
|
|
66
|
+
render: (t) => t.requirements.length === 0 ? "Nothing" : /* @__PURE__ */ jsx("span", { style: { display: "flex", flexWrap: "wrap", gap: 4 }, children: t.requirements.map((r) => /* @__PURE__ */ jsx("span", { style: TAG, children: r }, r)) })
|
|
90
67
|
},
|
|
91
68
|
{
|
|
92
|
-
key: "
|
|
93
|
-
header: "
|
|
94
|
-
render: (t) => t.
|
|
69
|
+
key: "enabled",
|
|
70
|
+
header: "Status",
|
|
71
|
+
render: (t) => t.enabled ? "Enabled" : "Disabled"
|
|
95
72
|
},
|
|
96
73
|
{
|
|
97
74
|
key: "actions",
|
|
@@ -128,8 +105,8 @@ function TiersTable({
|
|
|
128
105
|
getRowKey: (t) => t.id,
|
|
129
106
|
isLoading,
|
|
130
107
|
isError,
|
|
131
|
-
emptyTitle: currency && data?.length ? `No ${currency}
|
|
132
|
-
emptyDescription: `
|
|
108
|
+
emptyTitle: currency && data?.length ? `No ${currency} tiers` : "No tiers yet",
|
|
109
|
+
emptyDescription: `With no tier, every business resolves to verification required${currency ? ` in ${currency}` : ""}.`
|
|
133
110
|
}
|
|
134
111
|
),
|
|
135
112
|
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 { key: 'label', header: 'Tier', render: (t) => `${t.label} (${t.key})` },\n { key: 'currency', header: 'Currency', render: (t) => t.currency },\n { key: 'position', header: 'Position', render: (t) => t.position },\n {\n key: 'limits',\n header: 'Per transfer · daily · monthly',\n render: (t) =>\n [\n `${formatMinor(t.minimumMinor, t.currency)}–${formatMinor(t.maximumMinor, t.currency)}`,\n formatMinor(t.dailyMaximumMinor, t.currency),\n t.monthlyMaximumMinor\n ? formatMinor(t.monthlyMaximumMinor, t.currency)\n : 'no cap',\n ].join(' · '),\n },\n {\n key: 'requirements',\n header: 'Requires',\n render: (t) =>\n t.requirements.length === 0 ? (\n 'Nothing'\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: 'enabled',\n header: 'Status',\n render: (t) => (t.enabled ? 'Enabled' : 'Disabled'),\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} tiers` : 'No tiers 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":[]}
|
|
@@ -4,16 +4,22 @@ import { useState } from "react";
|
|
|
4
4
|
import { Button } from "@withpotter/ui-kit/button";
|
|
5
5
|
import {
|
|
6
6
|
CurrencyFilter,
|
|
7
|
-
Drawer,
|
|
8
7
|
PageHeader,
|
|
8
|
+
PageTabs,
|
|
9
9
|
currenciesIn,
|
|
10
|
-
useCurrencyParam
|
|
10
|
+
useCurrencyParam,
|
|
11
|
+
useTabParam
|
|
11
12
|
} from "../_ui";
|
|
12
13
|
import { TierPreviewTab } from "./tier-preview-tab";
|
|
13
14
|
import { TiersTable } from "./tiers-table";
|
|
14
15
|
import { useTiers } from "./use-tiers";
|
|
16
|
+
const TABS = [
|
|
17
|
+
{ id: "tiers", label: "Tiers" },
|
|
18
|
+
{ id: "preview", label: "Preview a business" }
|
|
19
|
+
];
|
|
20
|
+
const KEYS = TABS.map((t) => t.id);
|
|
15
21
|
function TiersWorkspace() {
|
|
16
|
-
const [
|
|
22
|
+
const [tab, setTab] = useTabParam(KEYS);
|
|
17
23
|
const [editing, setEditing] = useState(null);
|
|
18
24
|
const [currency, setCurrency] = useCurrencyParam();
|
|
19
25
|
const tiers = useTiers();
|
|
@@ -21,9 +27,9 @@ function TiersWorkspace() {
|
|
|
21
27
|
/* @__PURE__ */ jsx(
|
|
22
28
|
PageHeader,
|
|
23
29
|
{
|
|
24
|
-
title: "Payout
|
|
25
|
-
description: "
|
|
26
|
-
actions: /* @__PURE__ */ jsxs("div", { className: "adm-toolbar", children: [
|
|
30
|
+
title: "Payout tiers",
|
|
31
|
+
description: "Per-currency limits by verification level.",
|
|
32
|
+
actions: tab === "tiers" ? /* @__PURE__ */ jsxs("div", { className: "adm-toolbar", children: [
|
|
27
33
|
/* @__PURE__ */ jsx(
|
|
28
34
|
CurrencyFilter,
|
|
29
35
|
{
|
|
@@ -32,22 +38,13 @@ function TiersWorkspace() {
|
|
|
32
38
|
currencies: currenciesIn(tiers.data?.map((t) => t.currency))
|
|
33
39
|
}
|
|
34
40
|
),
|
|
35
|
-
/* @__PURE__ */ jsx(Button, {
|
|
36
|
-
|
|
37
|
-
] })
|
|
41
|
+
/* @__PURE__ */ jsx(Button, { onClick: () => setEditing("new"), children: "New tier" })
|
|
42
|
+
] }) : void 0
|
|
38
43
|
}
|
|
39
44
|
),
|
|
40
|
-
/* @__PURE__ */ jsx(
|
|
41
|
-
/* @__PURE__ */ jsx(
|
|
42
|
-
|
|
43
|
-
{
|
|
44
|
-
open: previewing,
|
|
45
|
-
onClose: () => setPreviewing(false),
|
|
46
|
-
title: "Preview a business",
|
|
47
|
-
subtitle: "Which level a merchant gets today, and what they still need to complete.",
|
|
48
|
-
children: /* @__PURE__ */ jsx(TierPreviewTab, {})
|
|
49
|
-
}
|
|
50
|
-
)
|
|
45
|
+
/* @__PURE__ */ jsx(PageTabs, { tabs: TABS, active: tab, onChange: setTab }),
|
|
46
|
+
tab === "tiers" && /* @__PURE__ */ jsx(TiersTable, { editing, onEdit: setEditing, currency }),
|
|
47
|
+
tab === "preview" && /* @__PURE__ */ jsx(TierPreviewTab, {})
|
|
51
48
|
] });
|
|
52
49
|
}
|
|
53
50
|
export {
|
|
@@ -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
|
|
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 PageHeader,\n PageTabs,\n currenciesIn,\n useCurrencyParam,\n useTabParam,\n} from '../_ui';\nimport { TierPreviewTab } from './tier-preview-tab';\nimport { TiersTable } from './tiers-table';\nimport { useTiers } from './use-tiers';\n\nconst TABS = [\n { id: 'tiers', label: 'Tiers' },\n { id: 'preview', label: 'Preview a business' },\n] as const;\n\nconst KEYS = TABS.map((t) => t.id);\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 [tab, setTab] = useTabParam(KEYS);\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 tiers\"\n description=\"Per-currency limits by verification level.\"\n actions={\n tab === 'tiers' ? (\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 onClick={() => setEditing('new')}>New tier</Button>\n </div>\n ) : undefined\n }\n />\n <PageTabs tabs={TABS} active={tab} onChange={setTab} />\n {tab === 'tiers' && (\n <TiersTable editing={editing} onEdit={setEditing} currency={currency} />\n )}\n {tab === 'preview' && <TierPreviewTab />}\n </>\n );\n}\n"],"mappings":";AAuCI,mBAOU,KADF,YANR;AArCJ,SAAS,gBAAgB;AACzB,SAAS,cAAc;AAEvB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,sBAAsB;AAC/B,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB;AAEzB,MAAM,OAAO;AAAA,EACX,EAAE,IAAI,SAAS,OAAO,QAAQ;AAAA,EAC9B,EAAE,IAAI,WAAW,OAAO,qBAAqB;AAC/C;AAEA,MAAM,OAAO,KAAK,IAAI,CAAC,MAAM,EAAE,EAAE;AAU1B,SAAS,iBAAiB;AAC/B,QAAM,CAAC,KAAK,MAAM,IAAI,YAAY,IAAI;AACtC,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,QAAQ,UACN,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,SAAS,MAAM,WAAW,KAAK,GAAG,sBAAQ;AAAA,WACpD,IACE;AAAA;AAAA,IAER;AAAA,IACA,oBAAC,YAAS,MAAM,MAAM,QAAQ,KAAK,UAAU,QAAQ;AAAA,IACpD,QAAQ,WACP,oBAAC,cAAW,SAAkB,QAAQ,YAAY,UAAoB;AAAA,IAEvE,QAAQ,aAAa,oBAAC,kBAAe;AAAA,KACxC;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":[]}
|