@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
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/pos/transactions-tab.tsx"],"sourcesContent":["'use client';\n\nimport { useDeferredValue, useEffect, useMemo, useState } from 'react';\nimport { Badge, Button, TableRow, Th, useToast } from '@withpotter/ui-kit';\nimport { toCsv } from '@withpotter/lib/csv';\nimport { adminPosApi } from '@withpotter/lib/admin-pos-api';\nimport { formatMinor } from '@withpotter/lib/money-minor';\nimport { FilterSearch, FilterSelect, Filters } from '../_ui';\nimport { ListCard } from '../_ui/list-card';\nimport list from '../_ui/list-card.module.css';\nimport { saveCsv } from '../revenue/download-button';\nimport {\n PAYMENTS_CSV_HEAD,\n PAYMENT_STATUS_OPTIONS,\n matchesSearch,\n paymentRow,\n statusWord,\n} from '../_pos/payments-model';\nimport { isForbidden, posErrorText } from '../_pos/pos-rules';\nimport { atTime } from '../_pos/pos-words';\nimport { usePosPayments, usePosTerminals } from '../_pos/use-pos';\nimport { useTenantParam } from '../_pos/use-tenant-param';\nimport pos from '../_pos/pos.module.css';\n\nconst PAGE_SIZE = 25;\n/** The export reads at most this many pages of 100, so one click cannot read the whole history. */\nconst EXPORT_MAX_PAGES = 20;\n\n/**\n * Card payments taken on every business's terminals, newest first, with search, filters, pages and a CSV.\n *\n * The API narrows by business and result and pages forward from the last row; it has no search. So the search looks\n * through the payments already loaded (more load as pages are turned), and the CSV reads every page that matches the\n * filters, then applies the search. Business names come from the terminals, which is as large a page as the API gives.\n */\nexport function TransactionsTab() {\n const { toast } = useToast();\n const [tenantId, setTenantId] = useTenantParam();\n const [status, setStatus] = useState('');\n const [search, setSearch] = useState('');\n const [page, setPage] = useState(0);\n const [exporting, setExporting] = useState(false);\n const term = useDeferredValue(search);\n\n const q = usePosPayments({ tenantId, status });\n const terminals = usePosTerminals({ limit: 100 });\n const names = useMemo(\n () =>\n new Map(terminals.data?.data.map((t) => [t.tenant.id, t.tenant.name])),\n [terminals.data],\n );\n const businessOptions = useMemo(\n () => [\n { value: '', label: 'Every business' },\n ...[...names].map(([value, label]) => ({ value, label })),\n ],\n [names],\n );\n\n const loaded = useMemo(\n () => q.data?.pages.flatMap((p) => p.rows) ?? [],\n [q.data],\n );\n const matched = useMemo(\n () => loaded.filter((p) => matchesSearch(p, names.get(p.tenantId), term)),\n [loaded, names, term],\n );\n const pages = Math.max(1, Math.ceil(matched.length / PAGE_SIZE));\n const last = page >= pages - 1;\n const rows = matched.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE);\n const narrowed = !!(tenantId || status || term.trim());\n\n // A new search or filter starts at the first page.\n useEffect(() => setPage(0), [tenantId, status, term]);\n\n const next = async () => {\n // At the end of what is loaded, fetch the page after it first.\n if (last && q.hasNextPage) await q.fetchNextPage();\n\n setPage((p) => p + 1);\n };\n\n const exportCsv = async () => {\n setExporting(true);\n\n try {\n const all = [];\n let cursor: string | undefined;\n let capped = false;\n\n for (let i = 0; i < EXPORT_MAX_PAGES; i += 1) {\n const got = await adminPosApi.payments({\n tenantId: tenantId || undefined,\n status: status || undefined,\n cursor,\n limit: 100,\n });\n\n all.push(...got.rows);\n cursor = got.nextCursor ?? undefined;\n\n if (!cursor) break;\n\n capped = i === EXPORT_MAX_PAGES - 1;\n }\n\n const body = all\n .filter((p) => matchesSearch(p, names.get(p.tenantId), term))\n .map((p) => paymentRow(p, names.get(p.tenantId)));\n\n saveCsv(\n `pos-transactions-${new Date().toISOString().slice(0, 10)}.csv`,\n toCsv([PAYMENTS_CSV_HEAD, ...body]),\n );\n\n if (capped)\n toast({\n title: `Exported the newest ${all.length} payments. Narrow the filters to reach older ones.`,\n });\n } catch {\n toast({ title: 'The export did not start. Please try again.' });\n } finally {\n setExporting(false);\n }\n };\n\n return (\n <ListCard\n title=\"Transactions\"\n count={q.hasNextPage ? undefined : matched.length}\n columns={6}\n minWidth={900}\n isLoading={q.isLoading}\n isError={q.isError && !q.data}\n onRetry={() => void q.refetch()}\n errorTitle={\n isForbidden(q.error)\n ? \"You don't have permission to see transactions.\"\n : posErrorText(q.error, 'Transactions did not load.')\n }\n isEmpty={rows.length === 0}\n emptyTitle={\n narrowed ? 'No transactions match.' : 'No card payments yet.'\n }\n controls={\n <Filters label=\"Filter transactions\">\n <FilterSearch\n value={search}\n onChange={setSearch}\n placeholder=\"Business, terminal, reference or amount\"\n label=\"Search transactions\"\n />\n <FilterSelect\n label=\"Business\"\n value={tenantId}\n onChange={setTenantId}\n options={businessOptions}\n searchable\n />\n <FilterSelect\n label=\"Result\"\n value={status}\n onChange={setStatus}\n options={[...PAYMENT_STATUS_OPTIONS]}\n />\n {narrowed && (\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onClick={() => {\n setSearch('');\n setStatus('');\n setTenantId('');\n }}\n >\n Clear filters\n </Button>\n )}\n <Button\n size=\"sm\"\n variant=\"secondary\"\n loading={exporting}\n onClick={() => void exportCsv()}\n >\n Export CSV\n </Button>\n </Filters>\n }\n head={\n <tr>\n <Th>Business</Th>\n <Th>Terminal</Th>\n <Th>Reference</Th>\n <Th>Result</Th>\n <Th>Amount</Th>\n <Th>When</Th>\n </tr>\n }\n footer={\n matched.length > 0 || q.hasNextPage ? (\n <div className={pos.toolbarRow}>\n <span className=\"adm-caption\">\n {`Page ${page + 1}${last && !q.hasNextPage ? ` of ${pages}` : ''}`}\n {term.trim() && q.hasNextPage\n ? ' · searching the payments loaded so far'\n : ''}\n </span>\n <div className=\"adm-toolbar\">\n <Button\n size=\"sm\"\n variant=\"secondary\"\n disabled={page === 0}\n onClick={() => setPage((p) => p - 1)}\n >\n Previous\n </Button>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n loading={q.isFetchingNextPage}\n disabled={last && !q.hasNextPage}\n onClick={() => void next()}\n >\n Next\n </Button>\n </div>\n </div>\n ) : undefined\n }\n >\n {rows.map((p) => (\n <TableRow key={p.id}>\n <td>{names.get(p.tenantId) ?? '–'}</td>\n <td>{p.deviceLabel ?? '–'}</td>\n <td className={list.muted}>{p.reference ?? '–'}</td>\n <td className={list.nowrap}>\n <Badge variant={p.status === 'SUCCEEDED' ? 'success' : 'secondary'}>\n {statusWord(p.status)}\n </Badge>\n </td>\n <td className={`${list.nowrap} ${pos.num}`}>\n {`${p.direction === 'out' ? '-' : ''}${formatMinor(p.amountMinor, p.currency)}`}\n </td>\n <td className={`${list.nowrap} ${list.muted}`}>{atTime(p.at)}</td>\n </TableRow>\n ))}\n </ListCard>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { EmptyNote, TableRow, TableShell, Th } from "@withpotter/ui-kit";
|
|
3
|
-
import { PICK_CURRENCY, Panel, moneyIn } from "../_ui";
|
|
4
|
-
import { channelRows } from "./channel-rows";
|
|
5
|
-
import styles from "./revenue.module.css";
|
|
6
|
-
const integer = new Intl.NumberFormat("en-US");
|
|
7
|
-
function ChannelPanel({
|
|
8
|
-
totals,
|
|
9
|
-
focus,
|
|
10
|
-
caption
|
|
11
|
-
}) {
|
|
12
|
-
if (!totals || totals.length === 0) return null;
|
|
13
|
-
const rows = channelRows(totals, focus);
|
|
14
|
-
const money = moneyIn(focus ?? "", 2);
|
|
15
|
-
return /* @__PURE__ */ jsx(Panel, { title: "Payment channels", caption, flush: true, children: !focus ? /* @__PURE__ */ jsx(EmptyNote, { className: styles.empty, children: PICK_CURRENCY }) : !rows ? /* @__PURE__ */ jsx(EmptyNote, { className: styles.empty, children: `No ${focus} payments through the gateway in this window.` }) : /* @__PURE__ */ jsx(
|
|
16
|
-
TableShell,
|
|
17
|
-
{
|
|
18
|
-
bare: true,
|
|
19
|
-
minWidth: 440,
|
|
20
|
-
density: "compact",
|
|
21
|
-
head: /* @__PURE__ */ jsxs("tr", { children: [
|
|
22
|
-
/* @__PURE__ */ jsx(Th, { children: "Channel" }),
|
|
23
|
-
/* @__PURE__ */ jsx(Th, { align: "right", children: "Payments" }),
|
|
24
|
-
/* @__PURE__ */ jsx(Th, { align: "right", children: "Amount" }),
|
|
25
|
-
/* @__PURE__ */ jsx(Th, { align: "right", children: "Share" })
|
|
26
|
-
] }),
|
|
27
|
-
children: rows.map((row) => /* @__PURE__ */ jsxs(TableRow, { children: [
|
|
28
|
-
/* @__PURE__ */ jsx("td", { className: styles.name, children: row.label }),
|
|
29
|
-
/* @__PURE__ */ jsx("td", { className: styles.num, children: integer.format(row.count) }),
|
|
30
|
-
/* @__PURE__ */ jsx("td", { className: styles.numStrong, children: money(row.amount) }),
|
|
31
|
-
/* @__PURE__ */ jsx("td", { className: styles.num, children: /* @__PURE__ */ jsxs("span", { className: styles.share, children: [
|
|
32
|
-
/* @__PURE__ */ jsx("span", { className: styles.shareTrack, "aria-hidden": true, children: /* @__PURE__ */ jsx(
|
|
33
|
-
"span",
|
|
34
|
-
{
|
|
35
|
-
className: styles.shareFill,
|
|
36
|
-
style: { width: `${Math.min(row.share, 100)}%` }
|
|
37
|
-
}
|
|
38
|
-
) }),
|
|
39
|
-
row.share.toFixed(1),
|
|
40
|
-
"%"
|
|
41
|
-
] }) })
|
|
42
|
-
] }, row.key))
|
|
43
|
-
}
|
|
44
|
-
) });
|
|
45
|
-
}
|
|
46
|
-
export {
|
|
47
|
-
ChannelPanel
|
|
48
|
-
};
|
|
49
|
-
//# sourceMappingURL=channel-panel.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/revenue/channel-panel.tsx"],"sourcesContent":["import { EmptyNote, TableRow, TableShell, Th } from '@withpotter/ui-kit';\nimport type { GatewayChannelTotal } from '@withpotter/lib/admin-api';\nimport { PICK_CURRENCY, Panel, moneyIn } from '../_ui';\nimport { channelRows } from './channel-rows';\nimport styles from './revenue.module.css';\n\nconst integer = new Intl.NumberFormat('en-US');\n\n/**\n * How customers paid at the gateway checkout (Card, USSD, Bank transfer, Opay),\n * within one currency: payments, amount and share of that currency. Hidden\n * until the api sends channels, so a box that cannot say shows nothing rather\n * than an empty table.\n */\nexport function ChannelPanel({\n totals,\n focus,\n caption,\n}: {\n totals: readonly GatewayChannelTotal[] | undefined;\n /** The one currency shown, or null when there are several and none is chosen. */\n focus: string | null;\n caption?: string;\n}) {\n if (!totals || totals.length === 0) return null;\n\n const rows = channelRows(totals, focus);\n const money = moneyIn(focus ?? '', 2);\n\n return (\n <Panel title=\"Payment channels\" caption={caption} flush>\n {!focus ? (\n <EmptyNote className={styles.empty}>{PICK_CURRENCY}</EmptyNote>\n ) : !rows ? (\n <EmptyNote className={styles.empty}>\n {`No ${focus} payments through the gateway in this window.`}\n </EmptyNote>\n ) : (\n <TableShell\n bare\n minWidth={440}\n density=\"compact\"\n head={\n <tr>\n <Th>Channel</Th>\n <Th align=\"right\">Payments</Th>\n <Th align=\"right\">Amount</Th>\n <Th align=\"right\">Share</Th>\n </tr>\n }\n >\n {rows.map((row) => (\n <TableRow key={row.key}>\n <td className={styles.name}>{row.label}</td>\n <td className={styles.num}>{integer.format(row.count)}</td>\n <td className={styles.numStrong}>{money(row.amount)}</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(row.share, 100)}%` }}\n />\n </span>\n {row.share.toFixed(1)}%\n </span>\n </td>\n </TableRow>\n ))}\n </TableShell>\n )}\n </Panel>\n );\n}\n"],"mappings":"AAgCQ,cAWI,YAXJ;AAhCR,SAAS,WAAW,UAAU,YAAY,UAAU;AAEpD,SAAS,eAAe,OAAO,eAAe;AAC9C,SAAS,mBAAmB;AAC5B,OAAO,YAAY;AAEnB,MAAM,UAAU,IAAI,KAAK,aAAa,OAAO;AAQtC,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AACF,GAKG;AACD,MAAI,CAAC,UAAU,OAAO,WAAW,EAAG,QAAO;AAE3C,QAAM,OAAO,YAAY,QAAQ,KAAK;AACtC,QAAM,QAAQ,QAAQ,SAAS,IAAI,CAAC;AAEpC,SACE,oBAAC,SAAM,OAAM,oBAAmB,SAAkB,OAAK,MACpD,WAAC,QACA,oBAAC,aAAU,WAAW,OAAO,OAAQ,yBAAc,IACjD,CAAC,OACH,oBAAC,aAAU,WAAW,OAAO,OAC1B,gBAAM,KAAK,iDACd,IAEA;AAAA,IAAC;AAAA;AAAA,MACC,MAAI;AAAA,MACJ,UAAU;AAAA,MACV,SAAQ;AAAA,MACR,MACE,qBAAC,QACC;AAAA,4BAAC,MAAG,qBAAO;AAAA,QACX,oBAAC,MAAG,OAAM,SAAQ,sBAAQ;AAAA,QAC1B,oBAAC,MAAG,OAAM,SAAQ,oBAAM;AAAA,QACxB,oBAAC,MAAG,OAAM,SAAQ,mBAAK;AAAA,SACzB;AAAA,MAGD,eAAK,IAAI,CAAC,QACT,qBAAC,YACC;AAAA,4BAAC,QAAG,WAAW,OAAO,MAAO,cAAI,OAAM;AAAA,QACvC,oBAAC,QAAG,WAAW,OAAO,KAAM,kBAAQ,OAAO,IAAI,KAAK,GAAE;AAAA,QACtD,oBAAC,QAAG,WAAW,OAAO,WAAY,gBAAM,IAAI,MAAM,GAAE;AAAA,QACpD,oBAAC,QAAG,WAAW,OAAO,KACpB,+BAAC,UAAK,WAAW,OAAO,OACtB;AAAA,8BAAC,UAAK,WAAW,OAAO,YAAY,eAAW,MAC7C;AAAA,YAAC;AAAA;AAAA,cACC,WAAW,OAAO;AAAA,cAClB,OAAO,EAAE,OAAO,GAAG,KAAK,IAAI,IAAI,OAAO,GAAG,CAAC,IAAI;AAAA;AAAA,UACjD,GACF;AAAA,UACC,IAAI,MAAM,QAAQ,CAAC;AAAA,UAAE;AAAA,WACxB,GACF;AAAA,WAda,IAAI,GAenB,CACD;AAAA;AAAA,EACH,GAEJ;AAEJ;","names":[]}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
function channelRows(totals, currency) {
|
|
2
|
-
if (!currency) return null;
|
|
3
|
-
const mine = (totals ?? []).filter((t) => t.currency === currency);
|
|
4
|
-
if (mine.length === 0) return null;
|
|
5
|
-
const all = mine.reduce((sum, t) => sum + Number(t.amountMinor), 0);
|
|
6
|
-
return mine.map((t) => ({
|
|
7
|
-
key: t.channel.key === "other" ? `other:${t.channel.label}` : t.channel.key,
|
|
8
|
-
label: t.channel.label,
|
|
9
|
-
count: t.count,
|
|
10
|
-
amount: Number(t.amountMinor) / 100,
|
|
11
|
-
share: all > 0 ? Number(t.amountMinor) / all * 100 : 0
|
|
12
|
-
})).sort((a, b) => b.amount - a.amount);
|
|
13
|
-
}
|
|
14
|
-
export {
|
|
15
|
-
channelRows
|
|
16
|
-
};
|
|
17
|
-
//# sourceMappingURL=channel-rows.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/revenue/channel-rows.ts"],"sourcesContent":["import type { GatewayChannelTotal } from '@withpotter/lib/admin-api';\n\nexport interface ChannelRow {\n key: string;\n label: string;\n count: number;\n /** In major units, for the money formatter. */\n amount: number;\n /** Percent of everything taken in this currency. */\n share: number;\n}\n\n/**\n * The channels for one currency, biggest first, each with its share of that\n * currency. Currencies are never added together, so no currency means no rows.\n * Null when there is nothing to show: the api did not send channels, or none\n * were taken in this currency.\n */\nexport function channelRows(\n totals: readonly GatewayChannelTotal[] | undefined,\n currency: string | null,\n): ChannelRow[] | null {\n if (!currency) return null;\n const mine = (totals ?? []).filter((t) => t.currency === currency);\n if (mine.length === 0) return null;\n\n const all = mine.reduce((sum, t) => sum + Number(t.amountMinor), 0);\n return mine\n .map((t) => ({\n key: t.channel.key === 'other' ? `other:${t.channel.label}` : t.channel.key,\n label: t.channel.label,\n count: t.count,\n amount: Number(t.amountMinor) / 100,\n share: all > 0 ? (Number(t.amountMinor) / all) * 100 : 0,\n }))\n .sort((a, b) => b.amount - a.amount);\n}\n"],"mappings":"AAkBO,SAAS,YACd,QACA,UACqB;AACrB,MAAI,CAAC,SAAU,QAAO;AACtB,QAAM,QAAQ,UAAU,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,aAAa,QAAQ;AACjE,MAAI,KAAK,WAAW,EAAG,QAAO;AAE9B,QAAM,MAAM,KAAK,OAAO,CAAC,KAAK,MAAM,MAAM,OAAO,EAAE,WAAW,GAAG,CAAC;AAClE,SAAO,KACJ,IAAI,CAAC,OAAO;AAAA,IACX,KAAK,EAAE,QAAQ,QAAQ,UAAU,SAAS,EAAE,QAAQ,KAAK,KAAK,EAAE,QAAQ;AAAA,IACxE,OAAO,EAAE,QAAQ;AAAA,IACjB,OAAO,EAAE;AAAA,IACT,QAAQ,OAAO,EAAE,WAAW,IAAI;AAAA,IAChC,OAAO,MAAM,IAAK,OAAO,EAAE,WAAW,IAAI,MAAO,MAAM;AAAA,EACzD,EAAE,EACD,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM;AACvC;","names":[]}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { jsx } from "react/jsx-runtime";
|
|
3
|
-
import { useState } from "react";
|
|
4
|
-
import { Button, useToast } from "@withpotter/ui-kit";
|
|
5
|
-
function saveCsv(filename, text) {
|
|
6
|
-
const url = URL.createObjectURL(
|
|
7
|
-
new Blob([`\uFEFF${text}`], { type: "text/csv;charset=utf-8" })
|
|
8
|
-
);
|
|
9
|
-
const link = document.createElement("a");
|
|
10
|
-
link.href = url;
|
|
11
|
-
link.download = filename;
|
|
12
|
-
document.body.appendChild(link);
|
|
13
|
-
link.click();
|
|
14
|
-
link.remove();
|
|
15
|
-
URL.revokeObjectURL(url);
|
|
16
|
-
}
|
|
17
|
-
function DownloadButton({
|
|
18
|
-
filename,
|
|
19
|
-
load,
|
|
20
|
-
label = "Download as CSV"
|
|
21
|
-
}) {
|
|
22
|
-
const [busy, setBusy] = useState(false);
|
|
23
|
-
const { toast } = useToast();
|
|
24
|
-
const run = async () => {
|
|
25
|
-
setBusy(true);
|
|
26
|
-
try {
|
|
27
|
-
saveCsv(filename, await load());
|
|
28
|
-
} catch {
|
|
29
|
-
toast({ title: "The download did not start. Please try again." });
|
|
30
|
-
} finally {
|
|
31
|
-
setBusy(false);
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
return /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "sm", loading: busy, onClick: () => void run(), children: label });
|
|
35
|
-
}
|
|
36
|
-
export {
|
|
37
|
-
DownloadButton,
|
|
38
|
-
saveCsv
|
|
39
|
-
};
|
|
40
|
-
//# sourceMappingURL=download-button.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/revenue/download-button.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { Button, useToast } from '@withpotter/ui-kit';\n\n/** Saves what `load` returns (CSV text) as a file. A byte-order mark is put first so Excel reads names and ₦ correctly. */\nexport function saveCsv(filename: string, text: string): void {\n const url = URL.createObjectURL(\n new Blob([`${text}`], { type: 'text/csv;charset=utf-8' }),\n );\n const link = document.createElement('a');\n link.href = url;\n link.download = filename;\n document.body.appendChild(link);\n link.click();\n link.remove();\n URL.revokeObjectURL(url);\n}\n\n/** One button that downloads the table as it is filtered. */\nexport function DownloadButton({\n filename,\n load,\n label = 'Download as CSV',\n}: {\n filename: string;\n load: () => Promise<string>;\n label?: string;\n}) {\n const [busy, setBusy] = useState(false);\n const { toast } = useToast();\n\n const run = async () => {\n setBusy(true);\n try {\n saveCsv(filename, await load());\n } catch {\n toast({ title: 'The download did not start. Please try again.' });\n } finally {\n setBusy(false);\n }\n };\n\n return (\n <Button variant=\"secondary\" size=\"sm\" loading={busy} onClick={() => void run()}>\n {label}\n </Button>\n );\n}\n"],"mappings":";AA4CI;AA1CJ,SAAS,gBAAgB;AACzB,SAAS,QAAQ,gBAAgB;AAG1B,SAAS,QAAQ,UAAkB,MAAoB;AAC5D,QAAM,MAAM,IAAI;AAAA,IACd,IAAI,KAAK,CAAC,SAAI,IAAI,EAAE,GAAG,EAAE,MAAM,yBAAyB,CAAC;AAAA,EAC3D;AACA,QAAM,OAAO,SAAS,cAAc,GAAG;AACvC,OAAK,OAAO;AACZ,OAAK,WAAW;AAChB,WAAS,KAAK,YAAY,IAAI;AAC9B,OAAK,MAAM;AACX,OAAK,OAAO;AACZ,MAAI,gBAAgB,GAAG;AACzB;AAGO,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA,QAAQ;AACV,GAIG;AACD,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK;AACtC,QAAM,EAAE,MAAM,IAAI,SAAS;AAE3B,QAAM,MAAM,YAAY;AACtB,YAAQ,IAAI;AACZ,QAAI;AACF,cAAQ,UAAU,MAAM,KAAK,CAAC;AAAA,IAChC,QAAQ;AACN,YAAM,EAAE,OAAO,gDAAgD,CAAC;AAAA,IAClE,UAAE;AACA,cAAQ,KAAK;AAAA,IACf;AAAA,EACF;AAEA,SACE,oBAAC,UAAO,SAAQ,aAAY,MAAK,MAAK,SAAS,MAAM,SAAS,MAAM,KAAK,IAAI,GAC1E,iBACH;AAEJ;","names":[]}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { useChartTheme } from "@withpotter/components/charts";
|
|
3
|
-
const ORDER = [
|
|
4
|
-
"online",
|
|
5
|
-
"pos",
|
|
6
|
-
"payments",
|
|
7
|
-
"payouts",
|
|
8
|
-
"plans",
|
|
9
|
-
"settlements",
|
|
10
|
-
"cash",
|
|
11
|
-
"transfers",
|
|
12
|
-
"fx",
|
|
13
|
-
"lending"
|
|
14
|
-
];
|
|
15
|
-
function useSourceColors() {
|
|
16
|
-
const { series } = useChartTheme();
|
|
17
|
-
const colors = Object.fromEntries(
|
|
18
|
-
ORDER.map((source, i) => [source, series[i % series.length]])
|
|
19
|
-
);
|
|
20
|
-
return { ...colors, other: "var(--text-tertiary)", series };
|
|
21
|
-
}
|
|
22
|
-
export {
|
|
23
|
-
useSourceColors
|
|
24
|
-
};
|
|
25
|
-
//# sourceMappingURL=earnings-colors.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/admin/revenue/earnings/earnings-colors.ts"],"sourcesContent":["'use client';\n\nimport { useChartTheme } from '@withpotter/components/charts';\nimport type { EarningsSource } from '@withpotter/lib/admin-earnings-api';\n\n/**\n * One colour per source, the same on its card, its pie slice and its legend row. They come from the chart system's\n * validated palette (fixed order, checked in light and dark), never picked here; the palette has eight slots, so\n * \"other\" takes a neutral rather than a ninth hue. The most important sources take the first slots.\n */\nconst ORDER: readonly EarningsSource[] = [\n 'online',\n 'pos',\n 'payments',\n 'payouts',\n 'plans',\n 'settlements',\n 'cash',\n 'transfers',\n 'fx',\n 'lending',\n];\n\nexport function useSourceColors(): Record<EarningsSource, string> & {\n /** Colours for things that are not sources (payment methods, kept / cost), in palette order. */\n series: readonly string[];\n} {\n const { series } = useChartTheme();\n const colors = Object.fromEntries(\n ORDER.map((source, i) => [source, series[i % series.length]]),\n ) as Record<EarningsSource, string>;\n\n return { ...colors, other: 'var(--text-tertiary)', series };\n}\n"],"mappings":";AAEA,SAAS,qBAAqB;AAQ9B,MAAM,QAAmC;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,SAAS,kBAGd;AACA,QAAM,EAAE,OAAO,IAAI,cAAc;AACjC,QAAM,SAAS,OAAO;AAAA,IACpB,MAAM,IAAI,CAAC,QAAQ,MAAM,CAAC,QAAQ,OAAO,IAAI,OAAO,MAAM,CAAC,CAAC;AAAA,EAC9D;AAEA,SAAO,EAAE,GAAG,QAAQ,OAAO,wBAAwB,OAAO;AAC5D;","names":[]}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { DonutChart } from "@withpotter/components/charts";
|
|
4
|
-
import { pctChange } from "@withpotter/lib/admin-earnings-model";
|
|
5
|
-
import styles from "./earnings.module.css";
|
|
6
|
-
function PieWithDetails({
|
|
7
|
-
slices,
|
|
8
|
-
money,
|
|
9
|
-
centerLabel
|
|
10
|
-
}) {
|
|
11
|
-
const shown = slices.filter((s) => s.value > 0);
|
|
12
|
-
const total = shown.reduce((a, s) => a + s.value, 0);
|
|
13
|
-
return /* @__PURE__ */ jsxs("div", { className: styles.pie, children: [
|
|
14
|
-
/* @__PURE__ */ jsx(
|
|
15
|
-
DonutChart,
|
|
16
|
-
{
|
|
17
|
-
data: shown.map((s) => ({ label: s.label, value: s.value, color: s.color })),
|
|
18
|
-
format: money,
|
|
19
|
-
centerLabel,
|
|
20
|
-
size: 170
|
|
21
|
-
}
|
|
22
|
-
),
|
|
23
|
-
/* @__PURE__ */ jsx("ul", { className: styles.legend, children: shown.map((s) => /* @__PURE__ */ jsxs("li", { className: styles.legendRow, children: [
|
|
24
|
-
/* @__PURE__ */ jsx("span", { className: styles.swatch, style: { background: s.color }, "aria-hidden": true }),
|
|
25
|
-
/* @__PURE__ */ jsxs("span", { className: styles.legendText, children: [
|
|
26
|
-
/* @__PURE__ */ jsx("span", { className: styles.legendLabel, children: s.label }),
|
|
27
|
-
s.detail ? /* @__PURE__ */ jsx("span", { className: styles.legendDetail, children: s.detail }) : null
|
|
28
|
-
] }),
|
|
29
|
-
/* @__PURE__ */ jsxs("span", { className: styles.legendNumbers, children: [
|
|
30
|
-
/* @__PURE__ */ jsx("strong", { children: money(s.value) }),
|
|
31
|
-
/* @__PURE__ */ jsx("span", { children: total > 0 ? `${Math.round(s.value / total * 100)}%` : "\u2013" })
|
|
32
|
-
] })
|
|
33
|
-
] }, s.key)) })
|
|
34
|
-
] });
|
|
35
|
-
}
|
|
36
|
-
function CompareBars({
|
|
37
|
-
rows,
|
|
38
|
-
money
|
|
39
|
-
}) {
|
|
40
|
-
const widest = Math.max(1, ...rows.flatMap((r) => [r.value, r.previous]));
|
|
41
|
-
return /* @__PURE__ */ jsx("ul", { className: styles.cmp, children: rows.map((r) => {
|
|
42
|
-
const change = pctChange(r.value, r.previous);
|
|
43
|
-
const tone = change === null || change === 0 ? "" : change > 0 ? styles.up : styles.down;
|
|
44
|
-
return /* @__PURE__ */ jsxs("li", { className: styles.cmpRow, children: [
|
|
45
|
-
/* @__PURE__ */ jsxs("span", { className: styles.cmpHead, children: [
|
|
46
|
-
/* @__PURE__ */ jsx("span", { className: styles.swatch, style: { background: r.color }, "aria-hidden": true }),
|
|
47
|
-
/* @__PURE__ */ jsx("span", { className: styles.legendLabel, children: r.label }),
|
|
48
|
-
/* @__PURE__ */ jsx("span", { className: `${styles.cmpDelta} ${tone}`, children: change === null ? r.value > 0 ? "New" : "\u2013" : `${change > 0 ? "+" : change < 0 ? "\u2212" : ""}${Math.abs(change)}%` })
|
|
49
|
-
] }),
|
|
50
|
-
/* @__PURE__ */ jsx("span", { className: styles.cmpTrack, "aria-hidden": true, children: /* @__PURE__ */ jsx(
|
|
51
|
-
"span",
|
|
52
|
-
{
|
|
53
|
-
className: styles.cmpPrev,
|
|
54
|
-
style: { width: `${r.previous / widest * 100}%`, background: r.color }
|
|
55
|
-
}
|
|
56
|
-
) }),
|
|
57
|
-
/* @__PURE__ */ jsx("span", { className: styles.cmpTrack, "aria-hidden": true, children: /* @__PURE__ */ jsx(
|
|
58
|
-
"span",
|
|
59
|
-
{
|
|
60
|
-
className: styles.cmpNow,
|
|
61
|
-
style: { width: `${r.value / widest * 100}%`, background: r.color }
|
|
62
|
-
}
|
|
63
|
-
) }),
|
|
64
|
-
/* @__PURE__ */ jsxs("span", { className: styles.cmpNums, children: [
|
|
65
|
-
/* @__PURE__ */ jsx("strong", { children: money(r.value) }),
|
|
66
|
-
/* @__PURE__ */ jsx("span", { children: `before ${money(r.previous)}` })
|
|
67
|
-
] })
|
|
68
|
-
] }, r.key);
|
|
69
|
-
}) });
|
|
70
|
-
}
|
|
71
|
-
export {
|
|
72
|
-
CompareBars,
|
|
73
|
-
PieWithDetails
|
|
74
|
-
};
|
|
75
|
-
//# sourceMappingURL=earnings-legend.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/admin/revenue/earnings/earnings-legend.tsx"],"sourcesContent":["'use client';\n\nimport { DonutChart } from '@withpotter/components/charts';\nimport { pctChange } from '@withpotter/lib/admin-earnings-model';\nimport styles from './earnings.module.css';\n\nexport interface LegendSlice {\n key: string;\n label: string;\n value: number;\n color: string;\n /** A second line: \"12 transactions\". */\n detail?: string;\n}\n\n/**\n * A pie with its key beside it: every slice named in its own colour with its amount and share, so the chart can be\n * read without hovering. Slices of nothing are left out.\n */\nexport function PieWithDetails({\n slices,\n money,\n centerLabel,\n}: {\n slices: LegendSlice[];\n money: (value: number) => string;\n centerLabel: string;\n}) {\n const shown = slices.filter((s) => s.value > 0);\n const total = shown.reduce((a, s) => a + s.value, 0);\n\n return (\n <div className={styles.pie}>\n <DonutChart\n data={shown.map((s) => ({ label: s.label, value: s.value, color: s.color }))}\n format={money}\n centerLabel={centerLabel}\n size={170}\n />\n <ul className={styles.legend}>\n {shown.map((s) => (\n <li key={s.key} className={styles.legendRow}>\n <span className={styles.swatch} style={{ background: s.color }} aria-hidden />\n <span className={styles.legendText}>\n <span className={styles.legendLabel}>{s.label}</span>\n {s.detail ? <span className={styles.legendDetail}>{s.detail}</span> : null}\n </span>\n <span className={styles.legendNumbers}>\n <strong>{money(s.value)}</strong>\n <span>{total > 0 ? `${Math.round((s.value / total) * 100)}%` : '–'}</span>\n </span>\n </li>\n ))}\n </ul>\n </div>\n );\n}\n\nexport interface CompareRow {\n key: string;\n label: string;\n color: string;\n value: number;\n previous: number;\n}\n\n/**\n * Each stream this period against the period before, as paired bars in the stream's own colour: the faded bar is\n * before, the solid bar is now, and the change is said in words beside them.\n */\nexport function CompareBars({\n rows,\n money,\n}: {\n rows: CompareRow[];\n money: (value: number) => string;\n}) {\n const widest = Math.max(1, ...rows.flatMap((r) => [r.value, r.previous]));\n\n return (\n <ul className={styles.cmp}>\n {rows.map((r) => {\n const change = pctChange(r.value, r.previous);\n const tone =\n change === null || change === 0 ? '' : change > 0 ? styles.up : styles.down;\n\n return (\n <li key={r.key} className={styles.cmpRow}>\n <span className={styles.cmpHead}>\n <span className={styles.swatch} style={{ background: r.color }} aria-hidden />\n <span className={styles.legendLabel}>{r.label}</span>\n <span className={`${styles.cmpDelta} ${tone}`}>\n {change === null\n ? r.value > 0\n ? 'New'\n : '–'\n : `${change > 0 ? '+' : change < 0 ? '\\u2212' : ''}${Math.abs(change)}%`}\n </span>\n </span>\n <span className={styles.cmpTrack} aria-hidden>\n <span\n className={styles.cmpPrev}\n style={{ width: `${(r.previous / widest) * 100}%`, background: r.color }}\n />\n </span>\n <span className={styles.cmpTrack} aria-hidden>\n <span\n className={styles.cmpNow}\n style={{ width: `${(r.value / widest) * 100}%`, background: r.color }}\n />\n </span>\n <span className={styles.cmpNums}>\n <strong>{money(r.value)}</strong>\n <span>{`before ${money(r.previous)}`}</span>\n </span>\n </li>\n );\n })}\n </ul>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,308 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useState } from "react";
|
|
4
|
-
import Link from "next/link";
|
|
5
|
-
import { TimeSeriesChart } from "@withpotter/components/charts";
|
|
6
|
-
import {
|
|
7
|
-
earningsFigures,
|
|
8
|
-
pctChange,
|
|
9
|
-
toMajor
|
|
10
|
-
} from "@withpotter/lib/admin-earnings-model";
|
|
11
|
-
import { LoadFailed } from "../../audit/load-failed";
|
|
12
|
-
import { AdmLoading, Panel, StatCard, moneyIn, toTrend } from "../../_ui";
|
|
13
|
-
import { useSourceColors } from "./earnings-colors";
|
|
14
|
-
import { CompareBars, PieWithDetails } from "./earnings-legend";
|
|
15
|
-
import { useEarnings } from "./use-earnings";
|
|
16
|
-
import styles from "../revenue.module.css";
|
|
17
|
-
import ledger from "../ledger.module.css";
|
|
18
|
-
import earn from "./earnings.module.css";
|
|
19
|
-
const integer = new Intl.NumberFormat("en-US");
|
|
20
|
-
const ORDER = ["online", "pos", "payouts", "settlements", "cash", "plans", "payments", "other"];
|
|
21
|
-
const ALWAYS = /* @__PURE__ */ new Set(["online", "pos", "payouts", "settlements", "cash", "plans"]);
|
|
22
|
-
const TITLES = {
|
|
23
|
-
online: "Online card payments",
|
|
24
|
-
pos: "POS card payments and transfers",
|
|
25
|
-
payouts: "Payouts",
|
|
26
|
-
settlements: "Settlements to merchants' banks",
|
|
27
|
-
cash: "Cash received",
|
|
28
|
-
plans: "Plan subscriptions",
|
|
29
|
-
payments: "Other card payments",
|
|
30
|
-
other: "Other fees"
|
|
31
|
-
};
|
|
32
|
-
const WHAT = {
|
|
33
|
-
online: "What Olive keeps when a customer pays a merchant by card online, including money that arrives through the payment gateway.",
|
|
34
|
-
pos: "What Olive keeps when a customer pays at a merchant's POS terminal, by card or by transfer.",
|
|
35
|
-
payouts: "What Olive charges when a merchant sends money out of their wallet.",
|
|
36
|
-
settlements: "What Olive charges when a merchant's money is settled to their bank account.",
|
|
37
|
-
cash: "What Olive charges when cash is received for a merchant.",
|
|
38
|
-
plans: "What merchants pay Olive for their plan, however they paid.",
|
|
39
|
-
payments: "Card payment fees that are not tagged as online or POS.",
|
|
40
|
-
other: "Fees from anything not listed here."
|
|
41
|
-
};
|
|
42
|
-
const NOTHING_YET = (currency) => ({
|
|
43
|
-
currency,
|
|
44
|
-
exponent: 2,
|
|
45
|
-
feeMinor: "0",
|
|
46
|
-
providerMinor: "0",
|
|
47
|
-
netMinor: "0",
|
|
48
|
-
previousFeeMinor: "0",
|
|
49
|
-
previousProviderMinor: "0",
|
|
50
|
-
count: 0,
|
|
51
|
-
providerCostsRecorded: false,
|
|
52
|
-
bySource: [],
|
|
53
|
-
series: [],
|
|
54
|
-
previousSeries: [],
|
|
55
|
-
topMerchants: []
|
|
56
|
-
});
|
|
57
|
-
const MINOR = /* @__PURE__ */ new Set(["transfers", "fx", "lending", "other"]);
|
|
58
|
-
function named(parts) {
|
|
59
|
-
const rest = parts.filter((p) => MINOR.has(p.source));
|
|
60
|
-
const main = parts.filter((p) => !MINOR.has(p.source));
|
|
61
|
-
const sum = (k) => rest.reduce((a, p) => a + p[k], 0);
|
|
62
|
-
if (sum("value") <= 0 && sum("previous") <= 0) return main;
|
|
63
|
-
return [
|
|
64
|
-
...main,
|
|
65
|
-
{ source: "other", label: "Other fees", value: sum("value"), previous: sum("previous"), count: sum("count") }
|
|
66
|
-
];
|
|
67
|
-
}
|
|
68
|
-
function MetricCard({
|
|
69
|
-
on,
|
|
70
|
-
onPick,
|
|
71
|
-
span,
|
|
72
|
-
children
|
|
73
|
-
}) {
|
|
74
|
-
return /* @__PURE__ */ jsx(
|
|
75
|
-
"div",
|
|
76
|
-
{
|
|
77
|
-
className: `${span} ${earn.metric} ${on ? earn.metricOn : ""}`,
|
|
78
|
-
role: "button",
|
|
79
|
-
tabIndex: 0,
|
|
80
|
-
"aria-pressed": on,
|
|
81
|
-
onClick: onPick,
|
|
82
|
-
onKeyDown: (e) => {
|
|
83
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
84
|
-
e.preventDefault();
|
|
85
|
-
onPick();
|
|
86
|
-
}
|
|
87
|
-
},
|
|
88
|
-
children
|
|
89
|
-
}
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
const count = (n) => `${integer.format(n)} ${n === 1 ? "transaction" : "transactions"}`;
|
|
93
|
-
function EarningsSummary({
|
|
94
|
-
startDate,
|
|
95
|
-
endDate,
|
|
96
|
-
currency,
|
|
97
|
-
recurring,
|
|
98
|
-
span
|
|
99
|
-
}) {
|
|
100
|
-
const { data, isLoading, isError, refetch } = useEarnings({ startDate, endDate });
|
|
101
|
-
const colors = useSourceColors();
|
|
102
|
-
const [metric, setMetric] = useState("earned");
|
|
103
|
-
if (isError)
|
|
104
|
-
return /* @__PURE__ */ jsx(
|
|
105
|
-
LoadFailed,
|
|
106
|
-
{
|
|
107
|
-
message: "Earnings could not be loaded. This box may not have them yet.",
|
|
108
|
-
onRetry: () => void refetch()
|
|
109
|
-
}
|
|
110
|
-
);
|
|
111
|
-
if (isLoading || !data) return /* @__PURE__ */ jsx(AdmLoading, { label: "Loading what Olive earns\u2026" });
|
|
112
|
-
const wanted = currency || recurring.currency || "NGN";
|
|
113
|
-
const cur = data.currencies.find((c) => c.currency === wanted) ?? data.currencies[0] ?? NOTHING_YET(wanted);
|
|
114
|
-
const plan = recurring.currency === cur.currency ? recurring.collected ?? null : null;
|
|
115
|
-
const f = earningsFigures(cur, plan);
|
|
116
|
-
const have = named(f.parts);
|
|
117
|
-
const parts = ORDER.filter((k) => ALWAYS.has(k) || have.some((p) => p.source === k && (p.value !== 0 || p.previous !== 0))).map(
|
|
118
|
-
(k) => have.find((p) => p.source === k) ?? { source: k, label: TITLES[k], value: 0, previous: 0, count: 0 }
|
|
119
|
-
).sort((a, b) => ORDER.indexOf(a.source) - ORDER.indexOf(b.source));
|
|
120
|
-
const money = moneyIn(cur.currency, 2);
|
|
121
|
-
const caption = `${span[0].toUpperCase()}${span.slice(1)} \xB7 ${cur.currency}`;
|
|
122
|
-
const margin = f.gross > 0 ? Math.round(f.net / f.gross * 1e3) / 10 : null;
|
|
123
|
-
const green = colors.series[2];
|
|
124
|
-
const red = colors.series[7];
|
|
125
|
-
const byMethod = (recurring.collectedByMethod ?? []).filter((m) => m.amount > 0);
|
|
126
|
-
const day = (p) => {
|
|
127
|
-
const fee = toMajor(p.feeMinor, cur.exponent);
|
|
128
|
-
const cost = toMajor(p.providerMinor, cur.exponent);
|
|
129
|
-
return metric === "earned" ? fee : metric === "costs" ? cost : fee - cost;
|
|
130
|
-
};
|
|
131
|
-
const trend = toTrend(cur.series.map((p) => ({ date: p.date, value: day(p) })), "value");
|
|
132
|
-
const before = toTrend(cur.previousSeries.map((p) => ({ date: p.date, value: day(p) })), "value");
|
|
133
|
-
const feeSpark = cur.series.map((p) => toMajor(p.feeMinor, cur.exponent));
|
|
134
|
-
const chartTitle = { earned: "Fees earned each day", costs: "Processing costs each day", profit: "Profit each day" }[metric];
|
|
135
|
-
const topTotal = cur.topMerchants.reduce((a, m) => a + toMajor(m.feeMinor, cur.exponent), 0);
|
|
136
|
-
const label = (p) => TITLES[p.source] ?? p.label;
|
|
137
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
138
|
-
/* @__PURE__ */ jsxs("p", { className: ledger.sectionNote, children: [
|
|
139
|
-
"How money flows through Olive: what it earned, what it cost, what is left, and where it came from. Showing",
|
|
140
|
-
" ",
|
|
141
|
-
cur.currency,
|
|
142
|
-
data.currencies.length > 1 ? "; use the currency filter for the others." : "."
|
|
143
|
-
] }),
|
|
144
|
-
/* @__PURE__ */ jsxs("section", { "aria-label": "Earned, costs and profit", className: "adm-grid", children: [
|
|
145
|
-
/* @__PURE__ */ jsx(MetricCard, { on: metric === "earned", onPick: () => setMetric("earned"), span: "adm-span-3", children: /* @__PURE__ */ jsx(
|
|
146
|
-
StatCard,
|
|
147
|
-
{
|
|
148
|
-
label: "Total earned",
|
|
149
|
-
accent: colors.series[0],
|
|
150
|
-
spark: feeSpark,
|
|
151
|
-
sparkColor: colors.series[0],
|
|
152
|
-
value: money(f.gross),
|
|
153
|
-
sub: "Fees plus plan income",
|
|
154
|
-
delta: { pct: pctChange(f.gross, f.feesBefore + f.plan), caption: "vs the period before" },
|
|
155
|
-
hint: "Everything Olive earned in this period: fees on card payments, payouts, settlements and cash, plus what merchants paid for their plans."
|
|
156
|
-
}
|
|
157
|
-
) }),
|
|
158
|
-
/* @__PURE__ */ jsx(MetricCard, { on: metric === "costs", onPick: () => setMetric("costs"), span: "adm-span-3", children: /* @__PURE__ */ jsx(
|
|
159
|
-
StatCard,
|
|
160
|
-
{
|
|
161
|
-
label: "Processing costs",
|
|
162
|
-
accent: red,
|
|
163
|
-
value: money(cur.providerCostsRecorded ? f.provider : 0),
|
|
164
|
-
sub: cur.providerCostsRecorded ? "Charged by the processors" : "None recorded yet",
|
|
165
|
-
hint: "What the payment processors kept from the same payments, as far as the system records it. Until costs are recorded this stays at zero and profit equals what was earned."
|
|
166
|
-
}
|
|
167
|
-
) }),
|
|
168
|
-
/* @__PURE__ */ jsx(MetricCard, { on: metric === "profit", onPick: () => setMetric("profit"), span: "adm-span-3", children: /* @__PURE__ */ jsx(
|
|
169
|
-
StatCard,
|
|
170
|
-
{
|
|
171
|
-
label: "Profit",
|
|
172
|
-
accent: green,
|
|
173
|
-
value: money(f.net),
|
|
174
|
-
sub: cur.providerCostsRecorded ? "After processing costs" : "Same as earned until costs are recorded",
|
|
175
|
-
hint: "Total earned less the processing costs the system records. Running costs such as hosting and staff are not included."
|
|
176
|
-
}
|
|
177
|
-
) }),
|
|
178
|
-
/* @__PURE__ */ jsx("div", { className: "adm-span-3", children: /* @__PURE__ */ jsx(
|
|
179
|
-
StatCard,
|
|
180
|
-
{
|
|
181
|
-
label: "Margin",
|
|
182
|
-
accent: colors.series[3],
|
|
183
|
-
value: margin === null ? "\u2013" : `${margin}%`,
|
|
184
|
-
sub: "Profit as a share of earned",
|
|
185
|
-
hint: "Profit divided by total earned."
|
|
186
|
-
}
|
|
187
|
-
) })
|
|
188
|
-
] }),
|
|
189
|
-
/* @__PURE__ */ jsx("div", { className: "adm-grid", children: /* @__PURE__ */ jsx("div", { className: "adm-span-12", children: /* @__PURE__ */ jsx(Panel, { title: chartTitle, caption: `${caption} \xB7 dotted line is the period before`, height: "calc(var(--adm-panel-h) + 32px)", children: /* @__PURE__ */ jsx("div", { className: styles.flatChart, children: /* @__PURE__ */ jsx(
|
|
190
|
-
TimeSeriesChart,
|
|
191
|
-
{
|
|
192
|
-
data: trend,
|
|
193
|
-
color: metric === "costs" ? red : metric === "profit" ? green : colors.series[0],
|
|
194
|
-
formatValue: money,
|
|
195
|
-
compare: before,
|
|
196
|
-
compareLabel: "Period before",
|
|
197
|
-
emptyMessage: "Nothing was recorded on any day in this period",
|
|
198
|
-
height: 240
|
|
199
|
-
}
|
|
200
|
-
) }) }) }) }),
|
|
201
|
-
/* @__PURE__ */ jsx("h2", { className: ledger.sectionTitle, children: "Where the money comes from" }),
|
|
202
|
-
/* @__PURE__ */ jsxs("section", { "aria-label": "Earnings by stream", className: "adm-grid", children: [
|
|
203
|
-
parts.map((p) => /* @__PURE__ */ jsx("div", { className: "adm-span-4", children: /* @__PURE__ */ jsx(
|
|
204
|
-
StatCard,
|
|
205
|
-
{
|
|
206
|
-
label: label(p),
|
|
207
|
-
accent: colors[p.source],
|
|
208
|
-
value: money(p.value),
|
|
209
|
-
sub: p.source === "plans" ? "Collected in this period" : p.count === 0 ? "Nothing in this period" : count(p.count),
|
|
210
|
-
hint: WHAT[p.source],
|
|
211
|
-
delta: p.source === "plans" ? void 0 : { pct: pctChange(p.value, p.previous), caption: "vs the period before" }
|
|
212
|
-
}
|
|
213
|
-
) }, p.source)),
|
|
214
|
-
/* @__PURE__ */ jsx("div", { className: "adm-span-4", children: /* @__PURE__ */ jsx(
|
|
215
|
-
StatCard,
|
|
216
|
-
{
|
|
217
|
-
label: "Olive account and outlets",
|
|
218
|
-
accent: "var(--text-tertiary)",
|
|
219
|
-
value: "Not counted yet",
|
|
220
|
-
sub: "No earnings recorded",
|
|
221
|
-
hint: "Money received through the Olive account and outlets. The bank system does not report a fee for it yet, so nothing can be added here."
|
|
222
|
-
}
|
|
223
|
-
) })
|
|
224
|
-
] }),
|
|
225
|
-
/* @__PURE__ */ jsxs("div", { className: "adm-grid", children: [
|
|
226
|
-
/* @__PURE__ */ jsx("div", { className: "adm-span-4", children: /* @__PURE__ */ jsx(Panel, { title: "Where it comes from", caption, height: "auto", children: parts.some((p) => p.value > 0) ? /* @__PURE__ */ jsx(
|
|
227
|
-
PieWithDetails,
|
|
228
|
-
{
|
|
229
|
-
money,
|
|
230
|
-
centerLabel: "Earned",
|
|
231
|
-
slices: parts.map((p) => ({
|
|
232
|
-
key: p.source,
|
|
233
|
-
label: label(p),
|
|
234
|
-
value: p.value,
|
|
235
|
-
color: colors[p.source],
|
|
236
|
-
detail: p.source === "plans" ? "Collected" : count(p.count)
|
|
237
|
-
}))
|
|
238
|
-
}
|
|
239
|
-
) : /* @__PURE__ */ jsx("p", { className: styles.empty, children: "Nothing earned in this period." }) }) }),
|
|
240
|
-
/* @__PURE__ */ jsx("div", { className: "adm-span-4", children: /* @__PURE__ */ jsx(Panel, { title: "What Olive keeps", caption, height: "auto", children: cur.providerCostsRecorded && f.gross > 0 ? /* @__PURE__ */ jsx(
|
|
241
|
-
PieWithDetails,
|
|
242
|
-
{
|
|
243
|
-
money,
|
|
244
|
-
centerLabel: "Earned",
|
|
245
|
-
slices: [
|
|
246
|
-
{ key: "profit", label: "Profit", value: Math.max(0, f.net), color: green, detail: "After processing costs" },
|
|
247
|
-
{ key: "cost", label: "Processing costs", value: Math.max(0, f.provider), color: red, detail: "Charged by the processors" }
|
|
248
|
-
]
|
|
249
|
-
}
|
|
250
|
-
) : /* @__PURE__ */ jsx("p", { className: styles.empty, children: "No processing cost is recorded for these fees yet, so profit equals what was earned." }) }) }),
|
|
251
|
-
/* @__PURE__ */ jsx("div", { className: "adm-span-4", children: /* @__PURE__ */ jsx(Panel, { title: "How plans were paid", caption, height: "auto", children: byMethod.length > 0 && recurring.currency === cur.currency ? /* @__PURE__ */ jsx(
|
|
252
|
-
PieWithDetails,
|
|
253
|
-
{
|
|
254
|
-
money,
|
|
255
|
-
centerLabel: "Plans",
|
|
256
|
-
slices: byMethod.map((m, i) => ({
|
|
257
|
-
key: m.method,
|
|
258
|
-
label: m.method,
|
|
259
|
-
value: m.amount,
|
|
260
|
-
color: colors.series[i % colors.series.length],
|
|
261
|
-
detail: `${integer.format(m.payments)} ${m.payments === 1 ? "payment" : "payments"}`
|
|
262
|
-
}))
|
|
263
|
-
}
|
|
264
|
-
) : /* @__PURE__ */ jsx("p", { className: styles.empty, children: "No plan payments in this period." }) }) })
|
|
265
|
-
] }),
|
|
266
|
-
/* @__PURE__ */ jsxs("div", { className: "adm-grid", children: [
|
|
267
|
-
/* @__PURE__ */ jsx("div", { className: "adm-span-6", children: /* @__PURE__ */ jsx(Panel, { title: "Stream by stream, against the period before", caption, height: "auto", children: /* @__PURE__ */ jsx(
|
|
268
|
-
CompareBars,
|
|
269
|
-
{
|
|
270
|
-
money,
|
|
271
|
-
rows: parts.filter((p) => p.source !== "plans").map((p) => ({
|
|
272
|
-
key: p.source,
|
|
273
|
-
label: label(p),
|
|
274
|
-
color: colors[p.source],
|
|
275
|
-
value: p.value,
|
|
276
|
-
previous: p.previous
|
|
277
|
-
}))
|
|
278
|
-
}
|
|
279
|
-
) }) }),
|
|
280
|
-
/* @__PURE__ */ jsx("div", { className: "adm-span-6", children: /* @__PURE__ */ jsx(Panel, { title: "The merchants it comes from", caption: `Fees, ${caption}`, height: "auto", children: cur.topMerchants.length === 0 ? /* @__PURE__ */ jsx("p", { className: styles.empty, children: "No fees to show in this period." }) : /* @__PURE__ */ jsx("ol", { className: earn.rankList, children: cur.topMerchants.map((m, i) => {
|
|
281
|
-
const value = toMajor(m.feeMinor, cur.exponent);
|
|
282
|
-
const widest = toMajor(cur.topMerchants[0].feeMinor, cur.exponent);
|
|
283
|
-
return /* @__PURE__ */ jsxs("li", { className: earn.rankRow, children: [
|
|
284
|
-
/* @__PURE__ */ jsx("span", { className: earn.rankName, children: m.tenantId && m.name ? /* @__PURE__ */ jsx(Link, { href: `/admin/tenants/${m.tenantId}`, children: m.name }) : "No merchant recorded" }),
|
|
285
|
-
/* @__PURE__ */ jsx("strong", { className: earn.rankAmount, children: money(value) }),
|
|
286
|
-
/* @__PURE__ */ jsxs("span", { className: earn.rankSub, children: [
|
|
287
|
-
count(m.count),
|
|
288
|
-
topTotal > 0 ? ` \xB7 ${Math.round(value / topTotal * 100)}% of these` : ""
|
|
289
|
-
] }),
|
|
290
|
-
/* @__PURE__ */ jsx("span", { className: earn.rankBar, "aria-hidden": true, children: /* @__PURE__ */ jsx(
|
|
291
|
-
"span",
|
|
292
|
-
{
|
|
293
|
-
className: earn.rankFill,
|
|
294
|
-
style: {
|
|
295
|
-
width: `${widest > 0 ? Math.max(2, value / widest * 100) : 0}%`,
|
|
296
|
-
background: colors.series[i % colors.series.length]
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
) })
|
|
300
|
-
] }, `${m.tenantId ?? "none"}-${i}`);
|
|
301
|
-
}) }) }) })
|
|
302
|
-
] })
|
|
303
|
-
] });
|
|
304
|
-
}
|
|
305
|
-
export {
|
|
306
|
-
EarningsSummary
|
|
307
|
-
};
|
|
308
|
-
//# sourceMappingURL=earnings-summary.js.map
|