@withpotter/pages-admin 0.13.0-next.82 → 0.13.0-next.85
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/finder-format.js +1 -0
- package/dist/admin/_money/finder-format.js.map +1 -1
- package/dist/admin/_money/format.js +1 -0
- package/dist/admin/_money/format.js.map +1 -1
- package/dist/admin/_overview/attention.module.css +0 -3
- package/dist/admin/_overview/overdue-outstanding.js +2 -1
- package/dist/admin/_overview/overdue-outstanding.js.map +1 -1
- package/dist/admin/_ui/list-card.module.css +2 -16
- package/dist/admin/_ui/money-view.js +1 -0
- package/dist/admin/_ui/money-view.js.map +1 -1
- package/dist/admin/_ui/page-header.js +31 -4
- package/dist/admin/_ui/page-header.js.map +1 -1
- package/dist/admin/_ui/page-header.module.css +45 -0
- package/dist/admin/_ui/panel.module.css +0 -10
- 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 +1 -0
- 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 +0 -2
- package/dist/admin/feedback/prompt-preview.module.css +0 -2
- package/dist/admin/revenue/platform-fees.js +9 -0
- package/dist/admin/revenue/platform-fees.js.map +1 -0
- package/dist/admin/revenue/revenue-kpis.js +1 -1
- package/dist/admin/revenue/revenue-kpis.js.map +1 -1
- package/dist/admin/revenue/revenue-overview.js +20 -1
- package/dist/admin/revenue/revenue-overview.js.map +1 -1
- package/dist/admin/revenue/revenue-workspace.js +1 -1
- package/dist/admin/revenue/revenue-workspace.js.map +1 -1
- package/dist/admin/support/queue-card.module.css +1 -4
- package/dist/admin/tenants/[id]/commerce/format.js +2 -1
- package/dist/admin/tenants/[id]/commerce/format.js.map +1 -1
- package/dist/admin/tenants/[id]/page.js +31 -9
- package/dist/admin/tenants/[id]/page.js.map +1 -1
- package/dist/admin/tenants/[id]/tenant-layout.module.css +1 -4
- package/dist/admin/tenants/[id]/tenant-overview.js +3 -2
- 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-wallet-card.js +3 -2
- package/dist/admin/tenants/[id]/tenant-wallet-card.js.map +1 -1
- package/dist/admin/tenants/[id]/use-tenant-wallet.js +23 -0
- package/dist/admin/tenants/[id]/use-tenant-wallet.js.map +1 -0
- package/dist/admin/tenants/tenants-table.js +1 -1
- package/dist/admin/tenants/tenants-table.js.map +1 -1
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/revenue/revenue-workspace.tsx"],"sourcesContent":["'use client';\n\nimport { useMemo } from 'react';\nimport { ADMIN_NAME } from '../_shell/admin-brand';\nimport { usePlatformCockpit, usePlatformRevenue } from '../hooks';\nimport { LoadFailed } from '../audit/load-failed';\nimport {\n AdmLoading,\n CurrencyFilter,\n PageHeader,\n PageTabs,\n PeriodSelector,\n customRange,\n focusCurrency,\n moneyIn,\n periodPhrase,\n rangeFromKey,\n useCurrencyParam,\n usePersistedDateRange,\n useTabParam,\n} from '../_ui';\nimport { RevenueOverview } from './revenue-overview';\nimport { RevenueRetention } from './revenue-retention';\n\nconst TABS = [\n { id: 'overview', label: 'Overview' },\n { id: 'retention', label: 'Retention' },\n] as const;\n\nconst KEYS = TABS.map((t) => t.id);\n\n/** Revenue: headline money on Overview, revenue quality and cohorts on Retention. */\nexport function RevenueWorkspace() {\n const [tab, setTab] = useTabParam(KEYS);\n const {\n rangeKey,\n setRangeKey,\n customStart,\n customEnd,\n onCustomChange,\n } = usePersistedDateRange();\n // Merchant GMV comes back once per currency; `?currency=` narrows it.\n const [currency, setCurrency] = useCurrencyParam();\n\n const range = useMemo(\n () =>\n rangeKey === 'custom'\n ? customRange(customStart, customEnd)\n : rangeFromKey(rangeKey),\n [rangeKey, customStart, customEnd],\n );\n\n const { data, isLoading, isError, refetch } = usePlatformRevenue({\n startDate: range.startDate,\n endDate: range.endDate,\n });\n // Revenue health (MRR movement, logo churn, concentration) is computed as\n // part of the cockpit, not the revenue endpoint — fetched here too rather\n // than duplicating that aggregation.\n const cockpit = usePlatformCockpit({\n startDate: range.startDate,\n endDate: range.endDate,\n });\n\n const currencies = useMemo(() => data?.currencies ?? [], [data]);\n const focus = focusCurrency(currencies, currency);\n // Recurring revenue is the platform's own billing, in the currency the API\n // names for it (`currency`), shown as it comes, never converted.\n const recurringCurrency = data?.currency || '
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/revenue/revenue-workspace.tsx"],"sourcesContent":["'use client';\n\nimport { useMemo } from 'react';\nimport { ADMIN_NAME } from '../_shell/admin-brand';\nimport { usePlatformCockpit, usePlatformRevenue } from '../hooks';\nimport { LoadFailed } from '../audit/load-failed';\nimport {\n AdmLoading,\n CurrencyFilter,\n PageHeader,\n PageTabs,\n PeriodSelector,\n customRange,\n focusCurrency,\n moneyIn,\n periodPhrase,\n rangeFromKey,\n useCurrencyParam,\n usePersistedDateRange,\n useTabParam,\n} from '../_ui';\nimport { RevenueOverview } from './revenue-overview';\nimport { RevenueRetention } from './revenue-retention';\n\nconst TABS = [\n { id: 'overview', label: 'Overview' },\n { id: 'retention', label: 'Retention' },\n] as const;\n\nconst KEYS = TABS.map((t) => t.id);\n\n/** Revenue: headline money on Overview, revenue quality and cohorts on Retention. */\nexport function RevenueWorkspace() {\n const [tab, setTab] = useTabParam(KEYS);\n const {\n rangeKey,\n setRangeKey,\n customStart,\n customEnd,\n onCustomChange,\n } = usePersistedDateRange();\n // Merchant GMV comes back once per currency; `?currency=` narrows it.\n const [currency, setCurrency] = useCurrencyParam();\n\n const range = useMemo(\n () =>\n rangeKey === 'custom'\n ? customRange(customStart, customEnd)\n : rangeFromKey(rangeKey),\n [rangeKey, customStart, customEnd],\n );\n\n const { data, isLoading, isError, refetch } = usePlatformRevenue({\n startDate: range.startDate,\n endDate: range.endDate,\n });\n // Revenue health (MRR movement, logo churn, concentration) is computed as\n // part of the cockpit, not the revenue endpoint — fetched here too rather\n // than duplicating that aggregation.\n const cockpit = usePlatformCockpit({\n startDate: range.startDate,\n endDate: range.endDate,\n });\n\n const currencies = useMemo(() => data?.currencies ?? [], [data]);\n const focus = focusCurrency(currencies, currency);\n // Recurring revenue is the platform's own billing, in the currency the API\n // names for it (`currency`), shown as it comes, never converted.\n const recurringCurrency = data?.currency || 'NGN';\n const money = useMemo(() => moneyIn(recurringCurrency), [recurringCurrency]);\n const money2 = useMemo(\n () => moneyIn(recurringCurrency, 2),\n [recurringCurrency],\n );\n\n const span = periodPhrase(range);\n const period = `${span[0].toUpperCase()}${span.slice(1)}`;\n\n let body;\n if (isError) {\n body = (\n <LoadFailed\n message=\"Revenue for this period could not be loaded.\"\n onRetry={() => void refetch()}\n />\n );\n } else if (isLoading || !data) {\n body = <AdmLoading label=\"Loading revenue metrics…\" />;\n } else if (tab === 'retention') {\n body = (\n <RevenueRetention\n quality={data.quality}\n money={money}\n currency={currency}\n caption={currency ? `${period} · ${currency}` : period}\n />\n );\n } else {\n body = (\n <RevenueOverview\n data={data}\n health={cockpit.data?.health}\n money={money}\n money2={money2}\n currency={currency}\n focus={focus}\n period={period}\n span={span}\n recurringCaption={`${period} · ${recurringCurrency}`}\n />\n );\n }\n\n return (\n <div>\n <PageHeader\n title=\"Revenue\"\n description={`Every revenue on ${ADMIN_NAME} is reported here: its subscription income each month and year, and the merchant sales (GMV) it is based on.`}\n actions={\n <div className=\"adm-toolbar\">\n <CurrencyFilter\n value={currency}\n onChange={setCurrency}\n currencies={currencies}\n />\n <PeriodSelector\n value={rangeKey}\n onChange={setRangeKey}\n customStart={customStart}\n customEnd={customEnd}\n onCustomChange={onCustomChange}\n />\n </div>\n }\n />\n <PageTabs tabs={TABS} active={tab} onChange={setTab} />\n {body}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -75,11 +75,8 @@
|
|
|
75
75
|
outline-offset: -2px;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
/*
|
|
78
|
+
/* Column header with a hairline, not a shadow. */
|
|
79
79
|
.scroll thead th {
|
|
80
|
-
position: sticky;
|
|
81
|
-
top: 0;
|
|
82
|
-
z-index: 1;
|
|
83
80
|
background: var(--bg-base);
|
|
84
81
|
box-shadow: inset 0 -1px 0 var(--border-subtle);
|
|
85
82
|
white-space: nowrap;
|
|
@@ -3,7 +3,8 @@ function moneyIn(currency, decimals = 0) {
|
|
|
3
3
|
try {
|
|
4
4
|
return new Intl.NumberFormat("en-US", {
|
|
5
5
|
style: "currency",
|
|
6
|
-
|
|
6
|
+
currencyDisplay: "narrowSymbol",
|
|
7
|
+
currency: currency || "NGN",
|
|
7
8
|
maximumFractionDigits: decimals
|
|
8
9
|
}).format(n);
|
|
9
10
|
} catch {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../src/admin/tenants/%5Bid%5D/commerce/format.ts"],"sourcesContent":["import type { TenantProduct } from '@withpotter/lib/admin-api';\n\n/** Amounts in the merchant's own currency, whole units for headlines. */\nexport function moneyIn(currency: string, decimals = 0) {\n return (n: number) => {\n try {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: currency || '
|
|
1
|
+
{"version":3,"sources":["../../../../../src/admin/tenants/%5Bid%5D/commerce/format.ts"],"sourcesContent":["import type { TenantProduct } from '@withpotter/lib/admin-api';\n\n/** Amounts in the merchant's own currency, whole units for headlines. */\nexport function moneyIn(currency: string, decimals = 0) {\n return (n: number) => {\n try {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currencyDisplay: 'narrowSymbol',\n currency: currency || 'NGN',\n maximumFractionDigits: decimals,\n }).format(n);\n } catch {\n return `${currency} ${Math.round(n).toLocaleString()}`;\n }\n };\n}\n\nexport type Money = (n: number) => string;\n\n/** 1.2k / 3.4m, for figures in tight tiles. */\nexport const compact = (n: number) =>\n new Intl.NumberFormat('en-US', {\n notation: 'compact',\n maximumFractionDigits: 1,\n }).format(n);\n\nexport const shortDate = (iso: string) =>\n new Intl.DateTimeFormat('en', { month: 'short', day: 'numeric' }).format(\n new Date(iso),\n );\n\n/** Sentence-case a machine word: \"checked_in\" → \"Checked in\". */\nexport const humanize = (s: string) => {\n const t = s.replace(/[_-]+/g, ' ').trim();\n return t ? t[0].toUpperCase() + t.slice(1) : '–';\n};\n\nexport type PillTone =\n 'success' | 'pending' | 'failed' | 'processing' | 'neutral';\n\nconst TONE_OF: Record<string, PillTone> = {\n completed: 'success',\n paid: 'success',\n success: 'success',\n settled: 'success',\n fulfilled: 'success',\n delivered: 'success',\n collected: 'success',\n pending: 'pending',\n unpaid: 'pending',\n awaiting_confirmation: 'pending',\n authorized: 'pending',\n unfulfilled: 'pending',\n partial: 'pending',\n confirmed: 'processing',\n processing: 'processing',\n checked_in: 'processing',\n ready: 'processing',\n failed: 'failed',\n reversed: 'failed',\n expired: 'failed',\n cancelled: 'neutral',\n refunded: 'neutral',\n partially_refunded: 'neutral',\n};\n\nexport const toneOf = (status: string): PillTone =>\n TONE_OF[status?.toLowerCase()] ?? 'neutral';\n\nexport type StockState = {\n label: string;\n tone: PillTone;\n available: number | null;\n};\n\n/** What a shelf looks like for one item, from the counts the API sends. */\nexport function stockOf(p: TenantProduct): StockState {\n if (!p.trackInventory || p.quantity == null)\n return { label: 'Not tracked', tone: 'neutral', available: null };\n\n const available = p.quantity - (p.reserved ?? 0);\n\n if (available <= 0)\n return { label: 'Out of stock', tone: 'failed', available };\n\n if (p.lowStockThreshold != null && available <= p.lowStockThreshold)\n return { label: `Low · ${available}`, tone: 'pending', available };\n\n return {\n label: `In stock · ${compact(available)}`,\n tone: 'success',\n available,\n };\n}\n"],"mappings":"AAGO,SAAS,QAAQ,UAAkB,WAAW,GAAG;AACtD,SAAO,CAAC,MAAc;AACpB,QAAI;AACF,aAAO,IAAI,KAAK,aAAa,SAAS;AAAA,QACpC,OAAO;AAAA,QACP,iBAAiB;AAAA,QACjB,UAAU,YAAY;AAAA,QACtB,uBAAuB;AAAA,MACzB,CAAC,EAAE,OAAO,CAAC;AAAA,IACb,QAAQ;AACN,aAAO,GAAG,QAAQ,IAAI,KAAK,MAAM,CAAC,EAAE,eAAe,CAAC;AAAA,IACtD;AAAA,EACF;AACF;AAKO,MAAM,UAAU,CAAC,MACtB,IAAI,KAAK,aAAa,SAAS;AAAA,EAC7B,UAAU;AAAA,EACV,uBAAuB;AACzB,CAAC,EAAE,OAAO,CAAC;AAEN,MAAM,YAAY,CAAC,QACxB,IAAI,KAAK,eAAe,MAAM,EAAE,OAAO,SAAS,KAAK,UAAU,CAAC,EAAE;AAAA,EAChE,IAAI,KAAK,GAAG;AACd;AAGK,MAAM,WAAW,CAAC,MAAc;AACrC,QAAM,IAAI,EAAE,QAAQ,UAAU,GAAG,EAAE,KAAK;AACxC,SAAO,IAAI,EAAE,CAAC,EAAE,YAAY,IAAI,EAAE,MAAM,CAAC,IAAI;AAC/C;AAKA,MAAM,UAAoC;AAAA,EACxC,WAAW;AAAA,EACX,MAAM;AAAA,EACN,SAAS;AAAA,EACT,SAAS;AAAA,EACT,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,uBAAuB;AAAA,EACvB,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,SAAS;AAAA,EACT,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,SAAS;AAAA,EACT,WAAW;AAAA,EACX,UAAU;AAAA,EACV,oBAAoB;AACtB;AAEO,MAAM,SAAS,CAAC,WACrB,QAAQ,QAAQ,YAAY,CAAC,KAAK;AAS7B,SAAS,QAAQ,GAA8B;AACpD,MAAI,CAAC,EAAE,kBAAkB,EAAE,YAAY;AACrC,WAAO,EAAE,OAAO,eAAe,MAAM,WAAW,WAAW,KAAK;AAElE,QAAM,YAAY,EAAE,YAAY,EAAE,YAAY;AAE9C,MAAI,aAAa;AACf,WAAO,EAAE,OAAO,gBAAgB,MAAM,UAAU,UAAU;AAE5D,MAAI,EAAE,qBAAqB,QAAQ,aAAa,EAAE;AAChD,WAAO,EAAE,OAAO,YAAS,SAAS,IAAI,MAAM,WAAW,UAAU;AAEnE,SAAO;AAAA,IACL,OAAO,iBAAc,QAAQ,SAAS,CAAC;AAAA,IACvC,MAAM;AAAA,IACN;AAAA,EACF;AACF;","names":[]}
|
|
@@ -14,6 +14,7 @@ import { moduleOn } from "../../_shell/nav-modules";
|
|
|
14
14
|
import { TenantOverview } from "./tenant-overview";
|
|
15
15
|
import { TenantPeoplePanel } from "./tenant-people-panel";
|
|
16
16
|
import { TenantWalletCard } from "./tenant-wallet-card";
|
|
17
|
+
import { useTenantWallet } from "./use-tenant-wallet";
|
|
17
18
|
import { LedgerCurrencyFilter, MerchantLedger } from "./merchant-ledger";
|
|
18
19
|
import { TenantSupport } from "./tenant-support";
|
|
19
20
|
import { TenantVerification } from "./verification/tenant-verification";
|
|
@@ -61,6 +62,7 @@ function moneyCur(n, currency) {
|
|
|
61
62
|
try {
|
|
62
63
|
return new Intl.NumberFormat("en-US", {
|
|
63
64
|
style: "currency",
|
|
65
|
+
currencyDisplay: "narrowSymbol",
|
|
64
66
|
currency,
|
|
65
67
|
maximumFractionDigits: 0
|
|
66
68
|
}).format(n);
|
|
@@ -68,6 +70,10 @@ function moneyCur(n, currency) {
|
|
|
68
70
|
return `${currency} ${n.toFixed(0)}`;
|
|
69
71
|
}
|
|
70
72
|
}
|
|
73
|
+
function sharedCurrency(rows) {
|
|
74
|
+
const codes = new Set(rows.map((r) => r.currency));
|
|
75
|
+
return codes.size === 1 ? [...codes][0] || null : null;
|
|
76
|
+
}
|
|
71
77
|
function num(n) {
|
|
72
78
|
return new Intl.NumberFormat("en-US").format(n ?? 0);
|
|
73
79
|
}
|
|
@@ -153,6 +159,11 @@ function TenantDetailPage({
|
|
|
153
159
|
startDate: range.startDate,
|
|
154
160
|
endDate: range.endDate
|
|
155
161
|
});
|
|
162
|
+
const { wallet, walletNote } = useTenantWallet(
|
|
163
|
+
id,
|
|
164
|
+
walletLedger,
|
|
165
|
+
data?.wallet
|
|
166
|
+
);
|
|
156
167
|
const queryClient = useQueryClient();
|
|
157
168
|
const statusMutation = useMutation({
|
|
158
169
|
mutationFn: (status) => platformAdminApi.setTenantStatus(id, status),
|
|
@@ -169,7 +180,8 @@ function TenantDetailPage({
|
|
|
169
180
|
return /* @__PURE__ */ jsx("p", { style: { color: "var(--error)" }, children: "Failed to load tenant." });
|
|
170
181
|
}
|
|
171
182
|
if (isLoading || !data) return /* @__PURE__ */ jsx(AdmLoading, { label: "Loading tenant\u2026" });
|
|
172
|
-
const
|
|
183
|
+
const view = { ...data, wallet };
|
|
184
|
+
const currency = data.currency ?? "NGN";
|
|
173
185
|
const currencyControl = /* @__PURE__ */ jsx(
|
|
174
186
|
CurrencyFilter,
|
|
175
187
|
{
|
|
@@ -177,7 +189,7 @@ function TenantDetailPage({
|
|
|
177
189
|
onChange: setShown,
|
|
178
190
|
currencies: currenciesIn(
|
|
179
191
|
data.currencies,
|
|
180
|
-
|
|
192
|
+
view.wallet?.balances.map((b) => b.currency),
|
|
181
193
|
data.recentOrders.map((o) => o.currency)
|
|
182
194
|
)
|
|
183
195
|
}
|
|
@@ -208,7 +220,8 @@ function TenantDetailPage({
|
|
|
208
220
|
tab === "overview" && /* @__PURE__ */ jsx(
|
|
209
221
|
TenantOverview,
|
|
210
222
|
{
|
|
211
|
-
data,
|
|
223
|
+
data: view,
|
|
224
|
+
walletNote,
|
|
212
225
|
periodLabel: periodPhrase(range),
|
|
213
226
|
periodControl,
|
|
214
227
|
recentOrders: /* @__PURE__ */ jsx(
|
|
@@ -265,7 +278,14 @@ function TenantDetailPage({
|
|
|
265
278
|
]
|
|
266
279
|
}
|
|
267
280
|
),
|
|
268
|
-
group === "wallet" && current !== "money" && /* @__PURE__ */ jsx(
|
|
281
|
+
group === "wallet" && current !== "money" && /* @__PURE__ */ jsx(
|
|
282
|
+
TenantWalletCard,
|
|
283
|
+
{
|
|
284
|
+
data: view,
|
|
285
|
+
currency: shown,
|
|
286
|
+
walletNote
|
|
287
|
+
}
|
|
288
|
+
),
|
|
269
289
|
current === "money" && /* @__PURE__ */ jsx(
|
|
270
290
|
MerchantLedger,
|
|
271
291
|
{
|
|
@@ -1550,15 +1570,17 @@ function InvoicesTab({ id }) {
|
|
|
1550
1570
|
if (isError) return /* @__PURE__ */ jsx(EmptyPanel, { children: "Failed to load invoices." });
|
|
1551
1571
|
const rows = data?.invoices ?? [];
|
|
1552
1572
|
const s = data?.stats;
|
|
1573
|
+
const invoiceCurrency = sharedCurrency(rows) ?? "NGN";
|
|
1574
|
+
const invoiceMoney = (n) => moneyCur(n, invoiceCurrency);
|
|
1553
1575
|
const statItems = [
|
|
1554
1576
|
{
|
|
1555
1577
|
label: "Revenue",
|
|
1556
|
-
value:
|
|
1578
|
+
value: invoiceMoney(s?.totalRevenue ?? 0),
|
|
1557
1579
|
hint: "Total amount collected from invoices that have been paid."
|
|
1558
1580
|
},
|
|
1559
1581
|
{
|
|
1560
1582
|
label: "Outstanding",
|
|
1561
|
-
value:
|
|
1583
|
+
value: invoiceMoney(s?.totalOutstanding ?? 0),
|
|
1562
1584
|
hint: "Amount still owed across sent and overdue invoices that have not been paid."
|
|
1563
1585
|
},
|
|
1564
1586
|
{
|
|
@@ -1754,7 +1776,7 @@ function DiscountsTab({ id }) {
|
|
|
1754
1776
|
/* @__PURE__ */ jsx("tbody", { children: rows.map((d) => /* @__PURE__ */ jsxs("tr", { children: [
|
|
1755
1777
|
/* @__PURE__ */ jsx("td", { style: tableStyles.cell, children: d.code }),
|
|
1756
1778
|
/* @__PURE__ */ jsx("td", { style: tableStyles.cell, children: d.name }),
|
|
1757
|
-
/* @__PURE__ */ jsx("td", { style: tableStyles.cell, children: d.discountType === "percentage" ? `${d.value}%` : moneyCur(d.value, "
|
|
1779
|
+
/* @__PURE__ */ jsx("td", { style: tableStyles.cell, children: d.discountType === "percentage" ? `${d.value}%` : moneyCur(d.value, "NGN") }),
|
|
1758
1780
|
/* @__PURE__ */ jsxs("td", { style: tableStyles.cellMuted, children: [
|
|
1759
1781
|
num(d.usageCount),
|
|
1760
1782
|
d.usageLimit ? ` / ${num(d.usageLimit)}` : ""
|
|
@@ -1779,7 +1801,7 @@ function SettlementsTab({ id, range }) {
|
|
|
1779
1801
|
if (isError) return /* @__PURE__ */ jsx(EmptyPanel, { children: "Failed to load settlements." });
|
|
1780
1802
|
if (isLoading || !data) return /* @__PURE__ */ jsx(AdmLoading, { label: "Loading settlements\u2026" });
|
|
1781
1803
|
const sum = data.summary;
|
|
1782
|
-
const cur = sum.currency || "
|
|
1804
|
+
const cur = sum.currency || "NGN";
|
|
1783
1805
|
const banks = data.bankAccounts ?? [];
|
|
1784
1806
|
const rows = data.settlements ?? [];
|
|
1785
1807
|
const statItems = [
|
|
@@ -1869,7 +1891,7 @@ function SubscriptionsTab({ id }) {
|
|
|
1869
1891
|
if (isLoading || !data) return /* @__PURE__ */ jsx(AdmLoading, { label: "Loading subscriptions\u2026" });
|
|
1870
1892
|
const stats = data.stats;
|
|
1871
1893
|
const rows = data.subscriptions ?? [];
|
|
1872
|
-
const mrrCurrency = rows[0]?.currency || "
|
|
1894
|
+
const mrrCurrency = rows[0]?.currency || "NGN";
|
|
1873
1895
|
const statItems = [
|
|
1874
1896
|
{
|
|
1875
1897
|
label: "Active",
|