@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/_overview/kpi-row.tsx"],"sourcesContent":["import type { PlatformCockpit } from '@withpotter/lib/admin-api';\nimport { MoneyLines, StatCard, rowFor } from '../_ui';\n\nconst integer = new Intl.NumberFormat();\n\n/** The four headline figures for the window, each opening its page. */\nexport function KpiRow({\n data,\n currency,\n focus,\n compare,\n caption,\n}: {\n data: PlatformCockpit;\n /** The currency filter's choice; empty shows a line per currency. */\n currency: string;\n /** The one currency the change and sparkline follow, or null with several. */\n focus: string | null;\n /** False for \"all time\": there is no earlier period to compare with. */\n compare: boolean;\n caption: string;\n}) {\n const delta = (kpi: { pct: number | null }) =>\n compare ? { pct: kpi.pct, caption: 'vs
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/_overview/kpi-row.tsx"],"sourcesContent":["import type { PlatformCockpit } from '@withpotter/lib/admin-api';\nimport { MoneyLines, StatCard, rowFor } from '../_ui';\n\nconst integer = new Intl.NumberFormat();\n\n/** The four headline figures for the window, each opening its page. */\nexport function KpiRow({\n data,\n currency,\n focus,\n compare,\n caption,\n}: {\n data: PlatformCockpit;\n /** The currency filter's choice; empty shows a line per currency. */\n currency: string;\n /** The one currency the change and sparkline follow, or null with several. */\n focus: string | null;\n /** False for \"all time\": there is no earlier period to compare with. */\n compare: boolean;\n caption: string;\n}) {\n const delta = (kpi: { pct: number | null }) =>\n compare ? { pct: kpi.pct, caption: 'vs prior period' } : undefined;\n // GMV's change and sparkline only mean something within one currency.\n const gmvKpi = rowFor(data.kpis.gmvByCurrency, focus);\n const gmvSpark = rowFor(\n data.trend.performanceSeriesByCurrency,\n focus,\n )?.series.map((p) => p.gmv);\n\n return (\n <section aria-label=\"Performance indicators\" className=\"adm-grid\">\n <div className=\"adm-span-3\">\n <StatCard\n label=\"Merchant GMV\"\n value={\n <MoneyLines\n rows={data.kpis.gmvByCurrency}\n currency={currency}\n amount={(k) => k.current}\n empty=\"No sales yet\"\n />\n }\n href=\"/admin/revenue\"\n delta={gmvKpi ? delta(gmvKpi) : undefined}\n sub={compare && gmvKpi ? undefined : caption}\n spark={gmvSpark}\n hint=\"Gross value of paid orders in the selected period, one line per currency. Currencies are never added together.\"\n />\n </div>\n <div className=\"adm-span-3\">\n <StatCard\n label=\"Orders\"\n value={integer.format(data.kpis.orders.current)}\n href=\"/admin/revenue\"\n delta={delta(data.kpis.orders)}\n sub={compare ? undefined : 'Paid orders, all time'}\n spark={data.sparklines.orders}\n hint=\"Paid orders across all merchant workspaces, in every currency.\"\n />\n </div>\n <div className=\"adm-span-3\">\n <StatCard\n label=\"New workspaces\"\n value={integer.format(data.kpis.newTenants.current)}\n href=\"/admin/tenants\"\n delta={delta(data.kpis.newTenants)}\n sub={\n compare\n ? undefined\n : `${integer.format(data.totals.totalTenants)} in total`\n }\n spark={data.sparklines.tenants}\n />\n </div>\n <div className=\"adm-span-3\">\n <StatCard\n label=\"New accounts\"\n value={integer.format(data.kpis.newAccounts.current)}\n href=\"/admin/accounts\"\n delta={delta(data.kpis.newAccounts)}\n sub={\n compare\n ? undefined\n : `${integer.format(data.totals.totalAccounts)} in total`\n }\n spark={data.sparklines.accounts}\n />\n </div>\n </section>\n );\n}\n"],"mappings":"AAgCI,SAKQ,KALR;AA/BJ,SAAS,YAAY,UAAU,cAAc;AAE7C,MAAM,UAAU,IAAI,KAAK,aAAa;AAG/B,SAAS,OAAO;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GASG;AACD,QAAM,QAAQ,CAAC,QACb,UAAU,EAAE,KAAK,IAAI,KAAK,SAAS,kBAAkB,IAAI;AAE3D,QAAM,SAAS,OAAO,KAAK,KAAK,eAAe,KAAK;AACpD,QAAM,WAAW;AAAA,IACf,KAAK,MAAM;AAAA,IACX;AAAA,EACF,GAAG,OAAO,IAAI,CAAC,MAAM,EAAE,GAAG;AAE1B,SACE,qBAAC,aAAQ,cAAW,0BAAyB,WAAU,YACrD;AAAA,wBAAC,SAAI,WAAU,cACb;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,OACE;AAAA,UAAC;AAAA;AAAA,YACC,MAAM,KAAK,KAAK;AAAA,YAChB;AAAA,YACA,QAAQ,CAAC,MAAM,EAAE;AAAA,YACjB,OAAM;AAAA;AAAA,QACR;AAAA,QAEF,MAAK;AAAA,QACL,OAAO,SAAS,MAAM,MAAM,IAAI;AAAA,QAChC,KAAK,WAAW,SAAS,SAAY;AAAA,QACrC,OAAO;AAAA,QACP,MAAK;AAAA;AAAA,IACP,GACF;AAAA,IACA,oBAAC,SAAI,WAAU,cACb;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,OAAO,QAAQ,OAAO,KAAK,KAAK,OAAO,OAAO;AAAA,QAC9C,MAAK;AAAA,QACL,OAAO,MAAM,KAAK,KAAK,MAAM;AAAA,QAC7B,KAAK,UAAU,SAAY;AAAA,QAC3B,OAAO,KAAK,WAAW;AAAA,QACvB,MAAK;AAAA;AAAA,IACP,GACF;AAAA,IACA,oBAAC,SAAI,WAAU,cACb;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,OAAO,QAAQ,OAAO,KAAK,KAAK,WAAW,OAAO;AAAA,QAClD,MAAK;AAAA,QACL,OAAO,MAAM,KAAK,KAAK,UAAU;AAAA,QACjC,KACE,UACI,SACA,GAAG,QAAQ,OAAO,KAAK,OAAO,YAAY,CAAC;AAAA,QAEjD,OAAO,KAAK,WAAW;AAAA;AAAA,IACzB,GACF;AAAA,IACA,oBAAC,SAAI,WAAU,cACb;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,OAAO,QAAQ,OAAO,KAAK,KAAK,YAAY,OAAO;AAAA,QACnD,MAAK;AAAA,QACL,OAAO,MAAM,KAAK,KAAK,WAAW;AAAA,QAClC,KACE,UACI,SACA,GAAG,QAAQ,OAAO,KAAK,OAAO,aAAa,CAAC;AAAA,QAElD,OAAO,KAAK,WAAW;AAAA;AAAA,IACzB,GACF;AAAA,KACF;AAEJ;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/_overview/leaders-panel.tsx"],"sourcesContent":["import Link from 'next/link';\nimport { TableShell, Th } from '@withpotter/ui-kit';\nimport type { PlatformCockpit } from '@withpotter/lib/admin-api';\nimport { logoOf } from '../commerce/merchant-meta';\nimport { EntityAvatar } from '../_fx';\nimport { PICK_CURRENCY, Panel, moneyIn } from '../_ui';\nimport styles from './figures.module.css';\n\nconst integer = new Intl.NumberFormat();\n\n/** Merchants by volume within one currency: rank, orders, GMV and share. */\nexport function LeadersPanel({\n tenants,\n focus,\n caption,\n}: {\n /** The leaders ranked within `focus` only. */\n tenants: PlatformCockpit['topTenants'];\n /** The one currency ranked, or null when there are several and none is chosen. */\n focus: string | null;\n caption: string;\n}) {\n const top = tenants[0]?.gmv || 1;\n const money = moneyIn(focus ?? '');\n\n return (\n <Panel\n title=\"
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/_overview/leaders-panel.tsx"],"sourcesContent":["import Link from 'next/link';\nimport { TableShell, Th } from '@withpotter/ui-kit';\nimport type { PlatformCockpit } from '@withpotter/lib/admin-api';\nimport { logoOf } from '../commerce/merchant-meta';\nimport { EntityAvatar } from '../_fx';\nimport { PICK_CURRENCY, Panel, moneyIn } from '../_ui';\nimport styles from './figures.module.css';\n\nconst integer = new Intl.NumberFormat();\n\n/** Merchants by volume within one currency: rank, orders, GMV and share. */\nexport function LeadersPanel({\n tenants,\n focus,\n caption,\n}: {\n /** The leaders ranked within `focus` only. */\n tenants: PlatformCockpit['topTenants'];\n /** The one currency ranked, or null when there are several and none is chosen. */\n focus: string | null;\n caption: string;\n}) {\n const top = tenants[0]?.gmv || 1;\n const money = moneyIn(focus ?? '');\n\n return (\n <Panel\n title=\"Leading merchants\"\n caption={caption}\n action={<Link href=\"/admin/tenants\">View all</Link>}\n flush\n >\n {tenants.length ? (\n <TableShell\n bare\n minWidth={320}\n density=\"compact\"\n head={\n <tr>\n <Th className=\"w-10\">#</Th>\n <Th>Merchant</Th>\n <Th align=\"right\">Orders</Th>\n <Th align=\"right\">GMV</Th>\n </tr>\n }\n >\n {tenants.map((t, index) => {\n const logo = logoOf(t);\n\n return (\n <tr key={t.id} className={styles.leaderRow}>\n <td className={styles.rank}>{index + 1}</td>\n <td className={styles.leaderName}>\n <span className={styles.leaderId}>\n {logo && (\n <EntityAvatar\n name={t.businessName}\n src={logo}\n size={28}\n />\n )}\n <Link href={`/admin/tenants/${t.id}`}>\n {t.businessName}\n </Link>\n </span>\n <span\n className={styles.share}\n style={{ width: `${Math.max((t.gmv / top) * 100, 2)}%` }}\n />\n </td>\n <td className={styles.num}>{integer.format(t.orders)}</td>\n <td className={styles.numStrong}>{money(t.gmv)}</td>\n </tr>\n );\n })}\n </TableShell>\n ) : (\n <p className={styles.empty}>\n {focus ? `No ${focus} merchant volume yet.` : PICK_CURRENCY}\n </p>\n )}\n </Panel>\n );\n}\n"],"mappings":"AA6Bc,cASF,YATE;AA7Bd,OAAO,UAAU;AACjB,SAAS,YAAY,UAAU;AAE/B,SAAS,cAAc;AACvB,SAAS,oBAAoB;AAC7B,SAAS,eAAe,OAAO,eAAe;AAC9C,OAAO,YAAY;AAEnB,MAAM,UAAU,IAAI,KAAK,aAAa;AAG/B,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AACF,GAMG;AACD,QAAM,MAAM,QAAQ,CAAC,GAAG,OAAO;AAC/B,QAAM,QAAQ,QAAQ,SAAS,EAAE;AAEjC,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAM;AAAA,MACN;AAAA,MACA,QAAQ,oBAAC,QAAK,MAAK,kBAAiB,sBAAQ;AAAA,MAC5C,OAAK;AAAA,MAEJ,kBAAQ,SACP;AAAA,QAAC;AAAA;AAAA,UACC,MAAI;AAAA,UACJ,UAAU;AAAA,UACV,SAAQ;AAAA,UACR,MACE,qBAAC,QACC;AAAA,gCAAC,MAAG,WAAU,QAAO,eAAC;AAAA,YACtB,oBAAC,MAAG,sBAAQ;AAAA,YACZ,oBAAC,MAAG,OAAM,SAAQ,oBAAM;AAAA,YACxB,oBAAC,MAAG,OAAM,SAAQ,iBAAG;AAAA,aACvB;AAAA,UAGD,kBAAQ,IAAI,CAAC,GAAG,UAAU;AACzB,kBAAM,OAAO,OAAO,CAAC;AAErB,mBACE,qBAAC,QAAc,WAAW,OAAO,WAC/B;AAAA,kCAAC,QAAG,WAAW,OAAO,MAAO,kBAAQ,GAAE;AAAA,cACvC,qBAAC,QAAG,WAAW,OAAO,YACpB;AAAA,qCAAC,UAAK,WAAW,OAAO,UACrB;AAAA,0BACC;AAAA,oBAAC;AAAA;AAAA,sBACC,MAAM,EAAE;AAAA,sBACR,KAAK;AAAA,sBACL,MAAM;AAAA;AAAA,kBACR;AAAA,kBAEF,oBAAC,QAAK,MAAM,kBAAkB,EAAE,EAAE,IAC/B,YAAE,cACL;AAAA,mBACF;AAAA,gBACA;AAAA,kBAAC;AAAA;AAAA,oBACC,WAAW,OAAO;AAAA,oBAClB,OAAO,EAAE,OAAO,GAAG,KAAK,IAAK,EAAE,MAAM,MAAO,KAAK,CAAC,CAAC,IAAI;AAAA;AAAA,gBACzD;AAAA,iBACF;AAAA,cACA,oBAAC,QAAG,WAAW,OAAO,KAAM,kBAAQ,OAAO,EAAE,MAAM,GAAE;AAAA,cACrD,oBAAC,QAAG,WAAW,OAAO,WAAY,gBAAM,EAAE,GAAG,GAAE;AAAA,iBArBxC,EAAE,EAsBX;AAAA,UAEJ,CAAC;AAAA;AAAA,MACH,IAEA,oBAAC,OAAE,WAAW,OAAO,OAClB,kBAAQ,MAAM,KAAK,0BAA0B,eAChD;AAAA;AAAA,EAEJ;AAEJ;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/_overview/overdue-outstanding.ts"],"sourcesContent":["import type { PlatformCockpit } from '@withpotter/lib/admin-api';\nimport { totalsByCurrency } from '../_ui/currency-filter-model';\n\ntype Overdue = PlatformCockpit['attention']['overdueInvoices'];\n\n/** A main-unit amount in its own currency: 120000 NGN → \"
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/_overview/overdue-outstanding.ts"],"sourcesContent":["import type { PlatformCockpit } from '@withpotter/lib/admin-api';\nimport { totalsByCurrency } from '../_ui/currency-filter-model';\n\ntype Overdue = PlatformCockpit['attention']['overdueInvoices'];\n\n/** A main-unit amount in its own currency: 120000 NGN → \"NGN 120,000\". */\nexport function inCurrency(value: number, code: string): string {\n try {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: code || 'USD',\n maximumFractionDigits: 0,\n }).format(value);\n } catch {\n return `${code} ${Math.round(value)}`;\n }\n}\n\n/**\n * What the overdue invoices still owe, one amount per currency, or null when\n * it cannot be said honestly. Amounts are never added across currencies.\n *\n * The engine sends `totalByCurrency` (every overdue invoice, per currency);\n * `selected` narrows it to the currency filter's choice. An engine without it\n * only has the sample: when the sample IS every overdue invoice it is added\n * up per currency; when there are more, nothing is shown rather than a\n * partial figure. The mixed `total` is never shown.\n */\nexport function overdueOutstanding(\n overdue: Overdue,\n selected = '',\n): string | null {\n if (!overdue.count) return null;\n\n let totals: Array<{ currency: string; amount: number }>;\n if (overdue.totalByCurrency) {\n totals = overdue.totalByCurrency.filter(\n (t) => !selected || t.currency === selected,\n );\n if (!totals.length) return selected ? `No ${selected} outstanding` : null;\n } else {\n if (overdue.sample.length < overdue.count) return null;\n totals = totalsByCurrency(\n overdue.sample,\n (i) => i.currency,\n (i) => i.amountDue,\n ).filter((t) => !selected || t.currency === selected);\n }\n\n const lines = totals.map((t) => inCurrency(t.amount, t.currency));\n\n return lines.length ? `${lines.join(' · ')} outstanding` : null;\n}\n"],"mappings":"AACA,SAAS,wBAAwB;AAK1B,SAAS,WAAW,OAAe,MAAsB;AAC9D,MAAI;AACF,WAAO,IAAI,KAAK,aAAa,SAAS;AAAA,MACpC,OAAO;AAAA,MACP,UAAU,QAAQ;AAAA,MAClB,uBAAuB;AAAA,IACzB,CAAC,EAAE,OAAO,KAAK;AAAA,EACjB,QAAQ;AACN,WAAO,GAAG,IAAI,IAAI,KAAK,MAAM,KAAK,CAAC;AAAA,EACrC;AACF;AAYO,SAAS,mBACd,SACA,WAAW,IACI;AACf,MAAI,CAAC,QAAQ,MAAO,QAAO;AAE3B,MAAI;AACJ,MAAI,QAAQ,iBAAiB;AAC3B,aAAS,QAAQ,gBAAgB;AAAA,MAC/B,CAAC,MAAM,CAAC,YAAY,EAAE,aAAa;AAAA,IACrC;AACA,QAAI,CAAC,OAAO,OAAQ,QAAO,WAAW,MAAM,QAAQ,iBAAiB;AAAA,EACvE,OAAO;AACL,QAAI,QAAQ,OAAO,SAAS,QAAQ,MAAO,QAAO;AAClD,aAAS;AAAA,MACP,QAAQ;AAAA,MACR,CAAC,MAAM,EAAE;AAAA,MACT,CAAC,MAAM,EAAE;AAAA,IACX,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,aAAa,QAAQ;AAAA,EACtD;AAEA,QAAM,QAAQ,OAAO,IAAI,CAAC,MAAM,WAAW,EAAE,QAAQ,EAAE,QAAQ,CAAC;AAEhE,SAAO,MAAM,SAAS,GAAG,MAAM,KAAK,QAAK,CAAC,iBAAiB;AAC7D;","names":[]}
|
|
@@ -13,8 +13,8 @@ function PortfolioPanel({
|
|
|
13
13
|
return /* @__PURE__ */ jsxs(
|
|
14
14
|
Panel,
|
|
15
15
|
{
|
|
16
|
-
title: "Merchant
|
|
17
|
-
caption: `${integer.format(total)}
|
|
16
|
+
title: "Merchant portfolio",
|
|
17
|
+
caption: `${integer.format(total)} workspaces`,
|
|
18
18
|
height: "var(--adm-panel-h)",
|
|
19
19
|
children: [
|
|
20
20
|
/* @__PURE__ */ jsx("div", { className: styles.stack, "aria-hidden": true, children: statuses.map((s) => /* @__PURE__ */ jsx(
|
|
@@ -29,17 +29,17 @@ function PortfolioPanel({
|
|
|
29
29
|
)) }),
|
|
30
30
|
/* @__PURE__ */ jsx("ul", { className: styles.legend, children: statuses.map((s) => /* @__PURE__ */ jsxs("li", { children: [
|
|
31
31
|
/* @__PURE__ */ jsx("i", { style: { background: statusColor(s.status) } }),
|
|
32
|
-
/* @__PURE__ */ jsx("span", { children: s.status
|
|
32
|
+
/* @__PURE__ */ jsx("span", { children: s.status }),
|
|
33
33
|
/* @__PURE__ */ jsx("strong", { children: integer.format(s.count) }),
|
|
34
34
|
/* @__PURE__ */ jsxs("small", { children: [
|
|
35
35
|
pct(s.count, total),
|
|
36
36
|
"%"
|
|
37
37
|
] })
|
|
38
38
|
] }, s.status)) }),
|
|
39
|
-
/* @__PURE__ */ jsx("div", { className: styles.subhead, children: "
|
|
39
|
+
/* @__PURE__ */ jsx("div", { className: styles.subhead, children: "Industries" }),
|
|
40
40
|
/* @__PURE__ */ jsx("ul", { className: styles.bars, children: industries.map((row, index) => /* @__PURE__ */ jsxs("li", { children: [
|
|
41
41
|
/* @__PURE__ */ jsxs("div", { className: styles.barLabel, children: [
|
|
42
|
-
/* @__PURE__ */ jsx("span", { children: row.industry || "
|
|
42
|
+
/* @__PURE__ */ jsx("span", { children: row.industry || "Unspecified" }),
|
|
43
43
|
/* @__PURE__ */ jsxs("strong", { children: [
|
|
44
44
|
integer.format(row.count),
|
|
45
45
|
/* @__PURE__ */ jsxs("small", { children: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/_overview/portfolio-panel.tsx"],"sourcesContent":["import type { PlatformCockpit } from '@withpotter/lib/admin-api';\nimport { Panel, categoryColor, statusColor } from '../_ui';\nimport styles from './figures.module.css';\n\nconst integer = new Intl.NumberFormat();\n\nconst pct = (count: number, total: number) =>\n total ? Math.round((count / total) * 100) : 0;\n\n/**\n * Where the merchant base sits: one status bar, then industries ranked. The\n * card is as tall as its row neighbours and the list scrolls inside it.\n */\nexport function PortfolioPanel({\n statuses,\n industries,\n}: {\n statuses: PlatformCockpit['statusBreakdown'];\n industries: PlatformCockpit['industryBreakdown'];\n}) {\n const total = statuses.reduce((sum, s) => sum + s.count, 0);\n const industryTotal = industries.reduce((sum, i) => sum + i.count, 0);\n const topIndustry = industries[0]?.count || 1;\n\n return (\n <Panel\n title=\"Merchant
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/_overview/portfolio-panel.tsx"],"sourcesContent":["import type { PlatformCockpit } from '@withpotter/lib/admin-api';\nimport { Panel, categoryColor, statusColor } from '../_ui';\nimport styles from './figures.module.css';\n\nconst integer = new Intl.NumberFormat();\n\nconst pct = (count: number, total: number) =>\n total ? Math.round((count / total) * 100) : 0;\n\n/**\n * Where the merchant base sits: one status bar, then industries ranked. The\n * card is as tall as its row neighbours and the list scrolls inside it.\n */\nexport function PortfolioPanel({\n statuses,\n industries,\n}: {\n statuses: PlatformCockpit['statusBreakdown'];\n industries: PlatformCockpit['industryBreakdown'];\n}) {\n const total = statuses.reduce((sum, s) => sum + s.count, 0);\n const industryTotal = industries.reduce((sum, i) => sum + i.count, 0);\n const topIndustry = industries[0]?.count || 1;\n\n return (\n <Panel\n title=\"Merchant portfolio\"\n caption={`${integer.format(total)} workspaces`}\n height=\"var(--adm-panel-h)\"\n >\n <div className={styles.stack} aria-hidden>\n {statuses.map((s) => (\n <span\n key={s.status}\n style={{\n width: `${pct(s.count, total)}%`,\n background: statusColor(s.status),\n }}\n />\n ))}\n </div>\n <ul className={styles.legend}>\n {statuses.map((s) => (\n <li key={s.status}>\n <i style={{ background: statusColor(s.status) }} />\n <span>{s.status}</span>\n <strong>{integer.format(s.count)}</strong>\n <small>{pct(s.count, total)}%</small>\n </li>\n ))}\n </ul>\n\n <div className={styles.subhead}>Industries</div>\n <ul className={styles.bars}>\n {industries.map((row, index) => (\n <li key={row.industry || 'unspecified'}>\n <div className={styles.barLabel}>\n <span>{row.industry || 'Unspecified'}</span>\n <strong>\n {integer.format(row.count)}\n <small> · {pct(row.count, industryTotal)}%</small>\n </strong>\n </div>\n <div className={styles.track}>\n <span\n style={{\n width: `${(row.count / topIndustry) * 100}%`,\n background: categoryColor(index),\n }}\n />\n </div>\n </li>\n ))}\n </ul>\n </Panel>\n );\n}\n"],"mappings":"AAgCU,cAeE,YAfF;AA/BV,SAAS,OAAO,eAAe,mBAAmB;AAClD,OAAO,YAAY;AAEnB,MAAM,UAAU,IAAI,KAAK,aAAa;AAEtC,MAAM,MAAM,CAAC,OAAe,UAC1B,QAAQ,KAAK,MAAO,QAAQ,QAAS,GAAG,IAAI;AAMvC,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AACF,GAGG;AACD,QAAM,QAAQ,SAAS,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,OAAO,CAAC;AAC1D,QAAM,gBAAgB,WAAW,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,OAAO,CAAC;AACpE,QAAM,cAAc,WAAW,CAAC,GAAG,SAAS;AAE5C,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAM;AAAA,MACN,SAAS,GAAG,QAAQ,OAAO,KAAK,CAAC;AAAA,MACjC,QAAO;AAAA,MAEP;AAAA,4BAAC,SAAI,WAAW,OAAO,OAAO,eAAW,MACtC,mBAAS,IAAI,CAAC,MACb;AAAA,UAAC;AAAA;AAAA,YAEC,OAAO;AAAA,cACL,OAAO,GAAG,IAAI,EAAE,OAAO,KAAK,CAAC;AAAA,cAC7B,YAAY,YAAY,EAAE,MAAM;AAAA,YAClC;AAAA;AAAA,UAJK,EAAE;AAAA,QAKT,CACD,GACH;AAAA,QACA,oBAAC,QAAG,WAAW,OAAO,QACnB,mBAAS,IAAI,CAAC,MACb,qBAAC,QACC;AAAA,8BAAC,OAAE,OAAO,EAAE,YAAY,YAAY,EAAE,MAAM,EAAE,GAAG;AAAA,UACjD,oBAAC,UAAM,YAAE,QAAO;AAAA,UAChB,oBAAC,YAAQ,kBAAQ,OAAO,EAAE,KAAK,GAAE;AAAA,UACjC,qBAAC,WAAO;AAAA,gBAAI,EAAE,OAAO,KAAK;AAAA,YAAE;AAAA,aAAC;AAAA,aAJtB,EAAE,MAKX,CACD,GACH;AAAA,QAEA,oBAAC,SAAI,WAAW,OAAO,SAAS,wBAAU;AAAA,QAC1C,oBAAC,QAAG,WAAW,OAAO,MACnB,qBAAW,IAAI,CAAC,KAAK,UACpB,qBAAC,QACC;AAAA,+BAAC,SAAI,WAAW,OAAO,UACrB;AAAA,gCAAC,UAAM,cAAI,YAAY,eAAc;AAAA,YACrC,qBAAC,YACE;AAAA,sBAAQ,OAAO,IAAI,KAAK;AAAA,cACzB,qBAAC,WAAM;AAAA;AAAA,gBAAI,IAAI,IAAI,OAAO,aAAa;AAAA,gBAAE;AAAA,iBAAC;AAAA,eAC5C;AAAA,aACF;AAAA,UACA,oBAAC,SAAI,WAAW,OAAO,OACrB;AAAA,YAAC;AAAA;AAAA,cACC,OAAO;AAAA,gBACL,OAAO,GAAI,IAAI,QAAQ,cAAe,GAAG;AAAA,gBACzC,YAAY,cAAc,KAAK;AAAA,cACjC;AAAA;AAAA,UACF,GACF;AAAA,aAfO,IAAI,YAAY,aAgBzB,CACD,GACH;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
|
|
@@ -9,18 +9,17 @@ function PositionPanel({
|
|
|
9
9
|
const perf = data.performance;
|
|
10
10
|
const rows = [
|
|
11
11
|
[
|
|
12
|
-
"Active
|
|
12
|
+
"Active workspaces",
|
|
13
13
|
integer.format(data.totals.activeTenants),
|
|
14
|
-
`of ${integer.format(data.totals.totalTenants)}
|
|
14
|
+
`of ${integer.format(data.totals.totalTenants)}`
|
|
15
15
|
],
|
|
16
16
|
[
|
|
17
17
|
"Merchants with sales",
|
|
18
18
|
perf ? integer.format(perf.transactingTenants) : "\u2014",
|
|
19
|
-
|
|
20
|
-
perf ? `${perf.transactingTenantRatePct}% of all merchants, this period` : void 0
|
|
19
|
+
perf ? `${perf.transactingTenantRatePct}% of active` : void 0
|
|
21
20
|
],
|
|
22
21
|
[
|
|
23
|
-
"
|
|
22
|
+
"Lifetime GMV",
|
|
24
23
|
/* @__PURE__ */ jsx(
|
|
25
24
|
MoneyLines,
|
|
26
25
|
{
|
|
@@ -47,12 +46,17 @@ function PositionPanel({
|
|
|
47
46
|
"this period, scaled to a year"
|
|
48
47
|
],
|
|
49
48
|
[
|
|
50
|
-
"
|
|
49
|
+
"Active subscriptions",
|
|
50
|
+
integer.format(data.totals.activeSubscriptions),
|
|
51
|
+
`${integer.format(data.totals.trialingSubscriptions)} trialing`
|
|
52
|
+
],
|
|
53
|
+
[
|
|
54
|
+
"Accounts",
|
|
51
55
|
integer.format(data.totals.totalAccounts),
|
|
52
|
-
data.accounts ? `${integer.format(data.accounts.active)} active
|
|
56
|
+
data.accounts ? `${integer.format(data.accounts.active)} active` : void 0
|
|
53
57
|
]
|
|
54
58
|
];
|
|
55
|
-
return /* @__PURE__ */ jsx(Panel, { title: "Platform
|
|
59
|
+
return /* @__PURE__ */ jsx(Panel, { title: "Platform position", caption: "Today", children: /* @__PURE__ */ jsx("dl", { className: styles.position, children: rows.map(([label, value, hint]) => /* @__PURE__ */ jsxs("div", { children: [
|
|
56
60
|
/* @__PURE__ */ jsx("dt", { children: label }),
|
|
57
61
|
/* @__PURE__ */ jsxs("dd", { children: [
|
|
58
62
|
/* @__PURE__ */ jsx("strong", { children: value }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/_overview/position-panel.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport type { PlatformCockpit } from '@withpotter/lib/admin-api';\nimport { MoneyLines, Panel } from '../_ui';\nimport styles from './position.module.css';\n\nconst integer = new Intl.NumberFormat();\n\n/** The platform's standing figures — not tied to the selected window. */\nexport function PositionPanel({\n data,\n currency,\n}: {\n data: PlatformCockpit;\n /** The currency filter's choice; empty shows a line per currency. */\n currency: string;\n}) {\n const perf = data.performance;\n const rows: Array<[string, ReactNode, string?]> = [\n [\n 'Active
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/_overview/position-panel.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport type { PlatformCockpit } from '@withpotter/lib/admin-api';\nimport { MoneyLines, Panel } from '../_ui';\nimport styles from './position.module.css';\n\nconst integer = new Intl.NumberFormat();\n\n/** The platform's standing figures — not tied to the selected window. */\nexport function PositionPanel({\n data,\n currency,\n}: {\n data: PlatformCockpit;\n /** The currency filter's choice; empty shows a line per currency. */\n currency: string;\n}) {\n const perf = data.performance;\n const rows: Array<[string, ReactNode, string?]> = [\n [\n 'Active workspaces',\n integer.format(data.totals.activeTenants),\n `of ${integer.format(data.totals.totalTenants)}`,\n ],\n [\n 'Merchants with sales',\n perf ? integer.format(perf.transactingTenants) : '—',\n perf ? `${perf.transactingTenantRatePct}% of active` : undefined,\n ],\n [\n 'Lifetime GMV',\n <MoneyLines\n key=\"gmv\"\n rows={data.totals.lifetimeGmvByCurrency}\n currency={currency}\n amount={(r) => r.amount}\n empty=\"No sales yet\"\n />,\n ],\n [\n 'Yearly sales pace',\n <MoneyLines\n key=\"run\"\n rows={perf?.byCurrency}\n currency={currency}\n amount={(r) => r.annualizedGmvRunRate}\n empty=\"—\"\n />,\n 'this period, scaled to a year',\n ],\n [\n 'Active subscriptions',\n integer.format(data.totals.activeSubscriptions),\n `${integer.format(data.totals.trialingSubscriptions)} trialing`,\n ],\n [\n 'Accounts',\n integer.format(data.totals.totalAccounts),\n data.accounts\n ? `${integer.format(data.accounts.active)} active`\n : undefined,\n ],\n ];\n\n return (\n <Panel title=\"Platform position\" caption=\"Today\">\n <dl className={styles.position}>\n {rows.map(([label, value, hint]) => (\n <div key={label}>\n <dt>{label}</dt>\n <dd>\n <strong>{value}</strong>\n {hint && <small>{hint}</small>}\n </dd>\n </div>\n ))}\n </dl>\n </Panel>\n );\n}\n"],"mappings":"AA8BM,cAuCM,YAvCN;AA5BN,SAAS,YAAY,aAAa;AAClC,OAAO,YAAY;AAEnB,MAAM,UAAU,IAAI,KAAK,aAAa;AAG/B,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AACF,GAIG;AACD,QAAM,OAAO,KAAK;AAClB,QAAM,OAA4C;AAAA,IAChD;AAAA,MACE;AAAA,MACA,QAAQ,OAAO,KAAK,OAAO,aAAa;AAAA,MACxC,MAAM,QAAQ,OAAO,KAAK,OAAO,YAAY,CAAC;AAAA,IAChD;AAAA,IACA;AAAA,MACE;AAAA,MACA,OAAO,QAAQ,OAAO,KAAK,kBAAkB,IAAI;AAAA,MACjD,OAAO,GAAG,KAAK,wBAAwB,gBAAgB;AAAA,IACzD;AAAA,IACA;AAAA,MACE;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UAEC,MAAM,KAAK,OAAO;AAAA,UAClB;AAAA,UACA,QAAQ,CAAC,MAAM,EAAE;AAAA,UACjB,OAAM;AAAA;AAAA,QAJF;AAAA,MAKN;AAAA,IACF;AAAA,IACA;AAAA,MACE;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UAEC,MAAM,MAAM;AAAA,UACZ;AAAA,UACA,QAAQ,CAAC,MAAM,EAAE;AAAA,UACjB,OAAM;AAAA;AAAA,QAJF;AAAA,MAKN;AAAA,MACA;AAAA,IACF;AAAA,IACA;AAAA,MACE;AAAA,MACA,QAAQ,OAAO,KAAK,OAAO,mBAAmB;AAAA,MAC9C,GAAG,QAAQ,OAAO,KAAK,OAAO,qBAAqB,CAAC;AAAA,IACtD;AAAA,IACA;AAAA,MACE;AAAA,MACA,QAAQ,OAAO,KAAK,OAAO,aAAa;AAAA,MACxC,KAAK,WACD,GAAG,QAAQ,OAAO,KAAK,SAAS,MAAM,CAAC,YACvC;AAAA,IACN;AAAA,EACF;AAEA,SACE,oBAAC,SAAM,OAAM,qBAAoB,SAAQ,SACvC,8BAAC,QAAG,WAAW,OAAO,UACnB,eAAK,IAAI,CAAC,CAAC,OAAO,OAAO,IAAI,MAC5B,qBAAC,SACC;AAAA,wBAAC,QAAI,iBAAM;AAAA,IACX,qBAAC,QACC;AAAA,0BAAC,YAAQ,iBAAM;AAAA,MACd,QAAQ,oBAAC,WAAO,gBAAK;AAAA,OACxB;AAAA,OALQ,KAMV,CACD,GACH,GACF;AAEJ;","names":[]}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { StatCard } from "../_ui";
|
|
4
4
|
import { amountLines } from "./pos-money";
|
|
5
|
-
import {
|
|
5
|
+
import { inPeriod } from "./pos-words";
|
|
6
6
|
import { usePosSummary } from "./use-pos";
|
|
7
7
|
import styles from "./pos.module.css";
|
|
8
8
|
function PosFigures({
|
|
@@ -11,7 +11,6 @@ function PosFigures({
|
|
|
11
11
|
currency = ""
|
|
12
12
|
}) {
|
|
13
13
|
const { data, isLoading, isError } = usePosSummary(period);
|
|
14
|
-
const total = usePosSummary(ALL_TIME);
|
|
15
14
|
const value = (n) => isError || n === void 0 ? "\u2013" : n.toLocaleString("en-US");
|
|
16
15
|
const money = data ? amountLines(data.paymentsAmount, currency) : [];
|
|
17
16
|
const countNote = currency ? ". The count is every currency" : "";
|
|
@@ -20,10 +19,10 @@ function PosFigures({
|
|
|
20
19
|
StatCard,
|
|
21
20
|
{
|
|
22
21
|
compact: true,
|
|
23
|
-
label: "
|
|
22
|
+
label: "Terminals",
|
|
24
23
|
isLoading,
|
|
25
|
-
value: value(data
|
|
26
|
-
sub: data ? `${data.
|
|
24
|
+
value: value(data?.terminals),
|
|
25
|
+
sub: data ? `${data.revoked} revoked` : void 0
|
|
27
26
|
}
|
|
28
27
|
),
|
|
29
28
|
/* @__PURE__ */ jsx(
|
|
@@ -72,19 +71,7 @@ function PosFigures({
|
|
|
72
71
|
label: "Card payments",
|
|
73
72
|
isLoading,
|
|
74
73
|
value: value(data?.paymentsCount),
|
|
75
|
-
sub: data ? money.length ? `${money.join(" \xB7 ")}, ${phrase}${countNote}` : `None ${currency ? `in ${currency} ` : ""}${inPeriod(
|
|
76
|
-
phrase
|
|
77
|
-
)}${countNote}` : void 0
|
|
78
|
-
}
|
|
79
|
-
),
|
|
80
|
-
/* @__PURE__ */ jsx(
|
|
81
|
-
StatCard,
|
|
82
|
-
{
|
|
83
|
-
compact: true,
|
|
84
|
-
label: "All-time transactions",
|
|
85
|
-
isLoading: total.isLoading,
|
|
86
|
-
value: value(total.data?.paymentsCount),
|
|
87
|
-
sub: total.data ? amountLines(total.data.paymentsAmount, currency).join(" \xB7 ") || "None yet" : void 0
|
|
74
|
+
sub: data ? money.length ? `${money.join(" \xB7 ")}, ${phrase}${countNote}` : `None ${currency ? `in ${currency} ` : ""}${inPeriod(phrase)}${countNote}` : void 0
|
|
88
75
|
}
|
|
89
76
|
)
|
|
90
77
|
] });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/_pos/pos-figures.tsx"],"sourcesContent":["'use client';\n\nimport type { PosWindow } from '@withpotter/lib/admin-pos-types';\nimport { StatCard } from '../_ui';\nimport { amountLines } from './pos-money';\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/_pos/pos-figures.tsx"],"sourcesContent":["'use client';\n\nimport type { PosWindow } from '@withpotter/lib/admin-pos-types';\nimport { StatCard } from '../_ui';\nimport { amountLines } from './pos-money';\nimport { inPeriod } from './pos-words';\nimport { usePosSummary } from './use-pos';\nimport styles from './pos.module.css';\n\n/**\n * The fleet at a glance: one number per card. Payments are counted in the\n * period and listed per currency, never added across currencies.\n */\nexport function PosFigures({\n period,\n phrase,\n currency = '',\n}: {\n period: PosWindow;\n phrase: string;\n /** The currency filter's choice; '' lists every currency. */\n currency?: string;\n}) {\n const { data, isLoading, isError } = usePosSummary(period);\n const value = (n: number | undefined) =>\n isError || n === undefined ? '–' : n.toLocaleString('en-US');\n const money = data ? amountLines(data.paymentsAmount, currency) : [];\n // The API counts payments across every currency, not per currency.\n const countNote = currency ? '. The count is every currency' : '';\n\n return (\n <section className={styles.figures} aria-label=\"POS at a glance\">\n <StatCard\n compact\n label=\"Terminals\"\n isLoading={isLoading}\n value={value(data?.terminals)}\n sub={data ? `${data.revoked} revoked` : undefined}\n />\n <StatCard\n compact\n label=\"Online now\"\n isLoading={isLoading}\n value={value(data?.online)}\n sub={data ? `Of ${data.terminals}` : undefined}\n />\n <StatCard\n compact\n label=\"Offline over a day\"\n isLoading={isLoading}\n value={value(data?.offlineOverDay)}\n alert={!!data?.offlineOverDay}\n />\n <StatCard\n compact\n label=\"Suspended\"\n isLoading={isLoading}\n value={value(data?.suspended)}\n />\n <StatCard\n compact\n label=\"Shifts open over 12 h\"\n isLoading={isLoading}\n value={value(data?.openShiftsOverTwelveHours)}\n alert={!!data?.openShiftsOverTwelveHours}\n />\n <StatCard\n compact\n label=\"Card payments\"\n isLoading={isLoading}\n value={value(data?.paymentsCount)}\n sub={\n data\n ? money.length\n ? `${money.join(' · ')}, ${phrase}${countNote}`\n : `None ${currency ? `in ${currency} ` : ''}${inPeriod(phrase)}${countNote}`\n : undefined\n }\n />\n </section>\n );\n}\n"],"mappings":";AA+BI,SACE,KADF;AA5BJ,SAAS,gBAAgB;AACzB,SAAS,mBAAmB;AAC5B,SAAS,gBAAgB;AACzB,SAAS,qBAAqB;AAC9B,OAAO,YAAY;AAMZ,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA,WAAW;AACb,GAKG;AACD,QAAM,EAAE,MAAM,WAAW,QAAQ,IAAI,cAAc,MAAM;AACzD,QAAM,QAAQ,CAAC,MACb,WAAW,MAAM,SAAY,WAAM,EAAE,eAAe,OAAO;AAC7D,QAAM,QAAQ,OAAO,YAAY,KAAK,gBAAgB,QAAQ,IAAI,CAAC;AAEnE,QAAM,YAAY,WAAW,kCAAkC;AAE/D,SACE,qBAAC,aAAQ,WAAW,OAAO,SAAS,cAAW,mBAC7C;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,SAAO;AAAA,QACP,OAAM;AAAA,QACN;AAAA,QACA,OAAO,MAAM,MAAM,SAAS;AAAA,QAC5B,KAAK,OAAO,GAAG,KAAK,OAAO,aAAa;AAAA;AAAA,IAC1C;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,SAAO;AAAA,QACP,OAAM;AAAA,QACN;AAAA,QACA,OAAO,MAAM,MAAM,MAAM;AAAA,QACzB,KAAK,OAAO,MAAM,KAAK,SAAS,KAAK;AAAA;AAAA,IACvC;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,SAAO;AAAA,QACP,OAAM;AAAA,QACN;AAAA,QACA,OAAO,MAAM,MAAM,cAAc;AAAA,QACjC,OAAO,CAAC,CAAC,MAAM;AAAA;AAAA,IACjB;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,SAAO;AAAA,QACP,OAAM;AAAA,QACN;AAAA,QACA,OAAO,MAAM,MAAM,SAAS;AAAA;AAAA,IAC9B;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,SAAO;AAAA,QACP,OAAM;AAAA,QACN;AAAA,QACA,OAAO,MAAM,MAAM,yBAAyB;AAAA,QAC5C,OAAO,CAAC,CAAC,MAAM;AAAA;AAAA,IACjB;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,SAAO;AAAA,QACP,OAAM;AAAA,QACN;AAAA,QACA,OAAO,MAAM,MAAM,aAAa;AAAA,QAChC,KACE,OACI,MAAM,SACJ,GAAG,MAAM,KAAK,QAAK,CAAC,KAAK,MAAM,GAAG,SAAS,KAC3C,QAAQ,WAAW,MAAM,QAAQ,MAAM,EAAE,GAAG,SAAS,MAAM,CAAC,GAAG,SAAS,KAC1E;AAAA;AAAA,IAER;AAAA,KACF;AAEJ;","names":[]}
|
|
@@ -17,7 +17,6 @@ const statusOf = (e) => e?.status;
|
|
|
17
17
|
const isStale = (e) => statusOf(e) === 409;
|
|
18
18
|
const isForbidden = (e) => statusOf(e) === 403;
|
|
19
19
|
const isNotFound = (e) => statusOf(e) === 404;
|
|
20
|
-
const isNotLinked = (e) => statusOf(e) === 400;
|
|
21
20
|
const STALE_TEXT = "This changed since you opened it. Refresh to see where it stands now.";
|
|
22
21
|
function posErrorText(error, fallback) {
|
|
23
22
|
const status = statusOf(error);
|
|
@@ -36,7 +35,6 @@ export {
|
|
|
36
35
|
isForbidden,
|
|
37
36
|
isLongOpen,
|
|
38
37
|
isNotFound,
|
|
39
|
-
isNotLinked,
|
|
40
38
|
isStale,
|
|
41
39
|
posErrorText,
|
|
42
40
|
reasonError,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/_pos/pos-rules.ts"],"sourcesContent":["import type {\n PosShift,\n PosTerminalStatus,\n} from '@withpotter/lib/admin-pos-types';\n\n/**\n * What an admin may do to a terminal or a shift, the forms' checks and what\n * an error says. Pure, so the rules the API enforces are tested here rather\n * than met as a 409.\n */\n\nexport const REASON_MAX = 300;\n\n/** A shift open this long is flagged; the API's own threshold. */\nexport const LONG_OPEN_HOURS = 12;\n\nexport type TerminalAction = 'suspend' | 'resume' | 'revoke';\n\n/**\n * Suspend needs active, resume needs suspended, revoke works from either\n * and is one-way. A revoked terminal has nothing left to do.\n */\nexport function terminalActions(status: PosTerminalStatus): TerminalAction[] {\n if (status === 'revoked') return [];\n\n return [status === 'active' ? 'suspend' : 'resume', 'revoke'];\n}\n\nexport const canCloseShift = (s: Pick<PosShift, 'status'>) =>\n s.status === 'open';\n\nexport const isLongOpen = (s: Pick<PosShift, 'status' | 'openHours'>) =>\n s.status === 'open' && (s.openHours ?? 0) >= LONG_OPEN_HOURS;\n\n/** An error for a reason, or undefined when it can be sent. */\nexport function reasonError(reason: string): string | undefined {\n const clean = reason.trim();\n\n if (!clean) return 'Say why, for the audit log.';\n\n if (clean.length > REASON_MAX)\n return `Keep the reason to ${REASON_MAX} characters.`;\n\n return undefined;\n}\n\nconst statusOf = (e: unknown) => (e as { status?: unknown } | null)?.status;\n\n/** The record changed under the admin: someone else acted first. */\nexport const isStale = (e: unknown) => statusOf(e) === 409;\n\nexport const isForbidden = (e: unknown) => statusOf(e) === 403;\n\nexport const isNotFound = (e: unknown) => statusOf(e) === 404;\n\
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/_pos/pos-rules.ts"],"sourcesContent":["import type {\n PosShift,\n PosTerminalStatus,\n} from '@withpotter/lib/admin-pos-types';\n\n/**\n * What an admin may do to a terminal or a shift, the forms' checks and what\n * an error says. Pure, so the rules the API enforces are tested here rather\n * than met as a 409.\n */\n\nexport const REASON_MAX = 300;\n\n/** A shift open this long is flagged; the API's own threshold. */\nexport const LONG_OPEN_HOURS = 12;\n\nexport type TerminalAction = 'suspend' | 'resume' | 'revoke';\n\n/**\n * Suspend needs active, resume needs suspended, revoke works from either\n * and is one-way. A revoked terminal has nothing left to do.\n */\nexport function terminalActions(status: PosTerminalStatus): TerminalAction[] {\n if (status === 'revoked') return [];\n\n return [status === 'active' ? 'suspend' : 'resume', 'revoke'];\n}\n\nexport const canCloseShift = (s: Pick<PosShift, 'status'>) =>\n s.status === 'open';\n\nexport const isLongOpen = (s: Pick<PosShift, 'status' | 'openHours'>) =>\n s.status === 'open' && (s.openHours ?? 0) >= LONG_OPEN_HOURS;\n\n/** An error for a reason, or undefined when it can be sent. */\nexport function reasonError(reason: string): string | undefined {\n const clean = reason.trim();\n\n if (!clean) return 'Say why, for the audit log.';\n\n if (clean.length > REASON_MAX)\n return `Keep the reason to ${REASON_MAX} characters.`;\n\n return undefined;\n}\n\nconst statusOf = (e: unknown) => (e as { status?: unknown } | null)?.status;\n\n/** The record changed under the admin: someone else acted first. */\nexport const isStale = (e: unknown) => statusOf(e) === 409;\n\nexport const isForbidden = (e: unknown) => statusOf(e) === 403;\n\nexport const isNotFound = (e: unknown) => statusOf(e) === 404;\n\nexport const STALE_TEXT =\n 'This changed since you opened it. Refresh to see where it stands now.';\n\n/**\n * An error in plain words. A 409 means the terminal or shift moved on, so\n * it says so and offers a refresh. A 400 keeps the server's line.\n */\nexport function posErrorText(error: unknown, fallback: string): string {\n const status = statusOf(error);\n const message = error instanceof Error ? error.message.trim() : '';\n\n if (status === 409) return STALE_TEXT;\n\n if (status === 403) return \"You don't have permission to do this.\";\n\n if (status === 404) return 'This no longer exists. Refresh the list.';\n\n if (status === 400 && message) return message;\n\n return fallback;\n}\n"],"mappings":"AAWO,MAAM,aAAa;AAGnB,MAAM,kBAAkB;AAQxB,SAAS,gBAAgB,QAA6C;AAC3E,MAAI,WAAW,UAAW,QAAO,CAAC;AAElC,SAAO,CAAC,WAAW,WAAW,YAAY,UAAU,QAAQ;AAC9D;AAEO,MAAM,gBAAgB,CAAC,MAC5B,EAAE,WAAW;AAER,MAAM,aAAa,CAAC,MACzB,EAAE,WAAW,WAAW,EAAE,aAAa,MAAM;AAGxC,SAAS,YAAY,QAAoC;AAC9D,QAAM,QAAQ,OAAO,KAAK;AAE1B,MAAI,CAAC,MAAO,QAAO;AAEnB,MAAI,MAAM,SAAS;AACjB,WAAO,sBAAsB,UAAU;AAEzC,SAAO;AACT;AAEA,MAAM,WAAW,CAAC,MAAgB,GAAmC;AAG9D,MAAM,UAAU,CAAC,MAAe,SAAS,CAAC,MAAM;AAEhD,MAAM,cAAc,CAAC,MAAe,SAAS,CAAC,MAAM;AAEpD,MAAM,aAAa,CAAC,MAAe,SAAS,CAAC,MAAM;AAEnD,MAAM,aACX;AAMK,SAAS,aAAa,OAAgB,UAA0B;AACrE,QAAM,SAAS,SAAS,KAAK;AAC7B,QAAM,UAAU,iBAAiB,QAAQ,MAAM,QAAQ,KAAK,IAAI;AAEhE,MAAI,WAAW,IAAK,QAAO;AAE3B,MAAI,WAAW,IAAK,QAAO;AAE3B,MAAI,WAAW,IAAK,QAAO;AAE3B,MAAI,WAAW,OAAO,QAAS,QAAO;AAEtC,SAAO;AACT;","names":[]}
|
|
@@ -69,8 +69,7 @@ const ADMIN_EVENT = {
|
|
|
69
69
|
"pos_terminal.revoke": "Revoked"
|
|
70
70
|
};
|
|
71
71
|
function historyLine(e) {
|
|
72
|
-
if (e.source === "admin_action")
|
|
73
|
-
return ADMIN_EVENT[e.event] ?? words(e.event);
|
|
72
|
+
if (e.source === "admin_action") return ADMIN_EVENT[e.event] ?? words(e.event);
|
|
74
73
|
return e.summary?.trim() || words(e.event);
|
|
75
74
|
}
|
|
76
75
|
function historyBy(e) {
|
|
@@ -83,10 +82,7 @@ function inPeriod(phrase) {
|
|
|
83
82
|
return `in ${phrase.startsWith("last") ? "the " : ""}${phrase}`;
|
|
84
83
|
}
|
|
85
84
|
const accessLine = (t) => t.operatorAccess === "any_member" ? "Anyone on the till roster can sign in" : "Only the people listed can sign in";
|
|
86
|
-
const givenOut = (s) => s.terminals + s.revoked;
|
|
87
|
-
const ALL_TIME = { from: "2020-01-01T00:00:00.000Z" };
|
|
88
85
|
export {
|
|
89
|
-
ALL_TIME,
|
|
90
86
|
SHIFT_STATUS_LABEL,
|
|
91
87
|
TERMINAL_STATUS_LABEL,
|
|
92
88
|
accessLine,
|
|
@@ -95,7 +91,6 @@ export {
|
|
|
95
91
|
cardLine,
|
|
96
92
|
closedLine,
|
|
97
93
|
count,
|
|
98
|
-
givenOut,
|
|
99
94
|
historyBy,
|
|
100
95
|
historyLine,
|
|
101
96
|
inPeriod,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/_pos/pos-words.ts"],"sourcesContent":["import type {\n PosHistoryEntry,\n PosShift,\n PosShiftStatus,\n PosTerminal,\n PosTerminalStatus,\n} from '@withpotter/lib/admin-pos-types';\n\n/** The words the POS screens use for what the API sends. Pure and tested. */\n\nconst MINUTE = 60_000;\nconst HOUR = 60 * MINUTE;\nconst DAY = 24 * HOUR;\n\nexport const TERMINAL_STATUS_LABEL: Record<PosTerminalStatus, string> = {\n active: 'Active',\n suspended: 'Suspended',\n revoked: 'Revoked',\n};\n\nexport const SHIFT_STATUS_LABEL: Record<PosShiftStatus, string> = {\n open: 'Open',\n closed: 'Closed',\n};\n\nconst CLOSED_REASON: Record<string, string> = {\n operator_sign_in_elsewhere: 'Signed in at another till',\n shift_expired: 'Ran out of time',\n manual_revoke: 'Closed from a dashboard',\n};\n\n/** Why a shift ended, in words; null while it is open. */\nexport function closedLine(s: Pick<PosShift, 'status' | 'closedReason'>) {\n if (s.status === 'open') return null;\n\n return (s.closedReason && CLOSED_REASON[s.closedReason]) ?? 'Closed';\n}\n\n/** \"15 Oct 2026\". */\nexport function onDay(iso: string): string {\n return new Date(iso).toLocaleDateString('en-GB', {\n day: 'numeric',\n month: 'short',\n year: 'numeric',\n });\n}\n\n/** \"15 Oct 2026, 14:03\". */\nexport function atTime(iso: string): string {\n const d = new Date(iso);\n\n return `${onDay(iso)}, ${d.toLocaleTimeString('en-GB', {\n hour: '2-digit',\n minute: '2-digit',\n })}`;\n}\n\n/** \"just now\", \"4 min ago\", \"3 h ago\", \"2 days ago\". */\nexport function ago(iso: string, now = Date.now()): string {\n const ms = Math.max(0, now - new Date(iso).getTime());\n\n if (ms < MINUTE) return 'just now';\n\n if (ms < HOUR) return `${Math.floor(ms / MINUTE)} min ago`;\n\n if (ms < DAY) return `${Math.floor(ms / HOUR)} h ago`;\n\n const days = Math.floor(ms / DAY);\n\n return days === 1 ? '1 day ago' : `${days} days ago`;\n}\n\n/**\n * How a terminal's connection reads. Online is green; offline for over a\n * day is amber, because someone should look; a stopped terminal (revoked\n * or suspended) and one never heard from are grey.\n */\nexport type OnlineTone = 'online' | 'offline' | 'stale' | 'never' | 'stopped';\n\ntype Seen = Pick<PosTerminal, 'online' | 'lastSeenAt' | 'status'>;\n\nexport function onlineTone(t: Seen, now = Date.now()): OnlineTone {\n if (t.status !== 'active') return 'stopped';\n\n if (t.online) return 'online';\n\n if (!t.lastSeenAt) return 'never';\n\n return now - new Date(t.lastSeenAt).getTime() > DAY ? 'stale' : 'offline';\n}\n\n/** \"Seen 4 min ago\", \"Offline · seen 3 days ago\", \"Never seen\". */\nexport function seenLine(t: Seen, now = Date.now()): string {\n if (!t.lastSeenAt) return 'Never seen';\n\n const when = ago(t.lastSeenAt, now);\n\n if (t.online) return `Seen ${when}`;\n\n return t.status === 'active' ? `Offline · seen ${when}` : `Last seen ${when}`;\n}\n\n/** \"Urovo · TID T100\", \"Needs a TID\", or nothing for a revoked terminal. */\nexport function cardLine(\n t: Pick<PosTerminal, 'provider' | 'tid' | 'status'>,\n): string | null {\n if (t.status === 'revoked') return null;\n\n if (!t.tid) return 'Needs a TID';\n\n const vendor = t.provider\n ? `${t.provider.charAt(0).toUpperCase()}${t.provider.slice(1)} · `\n : '';\n\n return `${vendor}TID ${t.tid}`;\n}\n\nconst words = (s: string) => {\n const w = s.replace(/[._-]+/g, ' ').trim();\n\n return w.charAt(0).toUpperCase() + w.slice(1);\n};\n\nconst ADMIN_EVENT: Record<string, string> = {\n 'pos_terminal.suspend': 'Suspended',\n 'pos_terminal.resume': 'Resumed',\n 'pos_terminal.revoke': 'Revoked',\n};\n\n/** One history entry as a sentence: what happened. */\nexport function historyLine(e: PosHistoryEntry): string {\n if (e.source === 'admin_action')
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/_pos/pos-words.ts"],"sourcesContent":["import type {\n PosHistoryEntry,\n PosShift,\n PosShiftStatus,\n PosTerminal,\n PosTerminalStatus,\n} from '@withpotter/lib/admin-pos-types';\n\n/** The words the POS screens use for what the API sends. Pure and tested. */\n\nconst MINUTE = 60_000;\nconst HOUR = 60 * MINUTE;\nconst DAY = 24 * HOUR;\n\nexport const TERMINAL_STATUS_LABEL: Record<PosTerminalStatus, string> = {\n active: 'Active',\n suspended: 'Suspended',\n revoked: 'Revoked',\n};\n\nexport const SHIFT_STATUS_LABEL: Record<PosShiftStatus, string> = {\n open: 'Open',\n closed: 'Closed',\n};\n\nconst CLOSED_REASON: Record<string, string> = {\n operator_sign_in_elsewhere: 'Signed in at another till',\n shift_expired: 'Ran out of time',\n manual_revoke: 'Closed from a dashboard',\n};\n\n/** Why a shift ended, in words; null while it is open. */\nexport function closedLine(s: Pick<PosShift, 'status' | 'closedReason'>) {\n if (s.status === 'open') return null;\n\n return (s.closedReason && CLOSED_REASON[s.closedReason]) ?? 'Closed';\n}\n\n/** \"15 Oct 2026\". */\nexport function onDay(iso: string): string {\n return new Date(iso).toLocaleDateString('en-GB', {\n day: 'numeric',\n month: 'short',\n year: 'numeric',\n });\n}\n\n/** \"15 Oct 2026, 14:03\". */\nexport function atTime(iso: string): string {\n const d = new Date(iso);\n\n return `${onDay(iso)}, ${d.toLocaleTimeString('en-GB', {\n hour: '2-digit',\n minute: '2-digit',\n })}`;\n}\n\n/** \"just now\", \"4 min ago\", \"3 h ago\", \"2 days ago\". */\nexport function ago(iso: string, now = Date.now()): string {\n const ms = Math.max(0, now - new Date(iso).getTime());\n\n if (ms < MINUTE) return 'just now';\n\n if (ms < HOUR) return `${Math.floor(ms / MINUTE)} min ago`;\n\n if (ms < DAY) return `${Math.floor(ms / HOUR)} h ago`;\n\n const days = Math.floor(ms / DAY);\n\n return days === 1 ? '1 day ago' : `${days} days ago`;\n}\n\n/**\n * How a terminal's connection reads. Online is green; offline for over a\n * day is amber, because someone should look; a stopped terminal (revoked\n * or suspended) and one never heard from are grey.\n */\nexport type OnlineTone = 'online' | 'offline' | 'stale' | 'never' | 'stopped';\n\ntype Seen = Pick<PosTerminal, 'online' | 'lastSeenAt' | 'status'>;\n\nexport function onlineTone(t: Seen, now = Date.now()): OnlineTone {\n if (t.status !== 'active') return 'stopped';\n\n if (t.online) return 'online';\n\n if (!t.lastSeenAt) return 'never';\n\n return now - new Date(t.lastSeenAt).getTime() > DAY ? 'stale' : 'offline';\n}\n\n/** \"Seen 4 min ago\", \"Offline · seen 3 days ago\", \"Never seen\". */\nexport function seenLine(t: Seen, now = Date.now()): string {\n if (!t.lastSeenAt) return 'Never seen';\n\n const when = ago(t.lastSeenAt, now);\n\n if (t.online) return `Seen ${when}`;\n\n return t.status === 'active' ? `Offline · seen ${when}` : `Last seen ${when}`;\n}\n\n/** \"Urovo · TID T100\", \"Needs a TID\", or nothing for a revoked terminal. */\nexport function cardLine(\n t: Pick<PosTerminal, 'provider' | 'tid' | 'status'>,\n): string | null {\n if (t.status === 'revoked') return null;\n\n if (!t.tid) return 'Needs a TID';\n\n const vendor = t.provider\n ? `${t.provider.charAt(0).toUpperCase()}${t.provider.slice(1)} · `\n : '';\n\n return `${vendor}TID ${t.tid}`;\n}\n\nconst words = (s: string) => {\n const w = s.replace(/[._-]+/g, ' ').trim();\n\n return w.charAt(0).toUpperCase() + w.slice(1);\n};\n\nconst ADMIN_EVENT: Record<string, string> = {\n 'pos_terminal.suspend': 'Suspended',\n 'pos_terminal.resume': 'Resumed',\n 'pos_terminal.revoke': 'Revoked',\n};\n\n/** One history entry as a sentence: what happened. */\nexport function historyLine(e: PosHistoryEntry): string {\n if (e.source === 'admin_action') return ADMIN_EVENT[e.event] ?? words(e.event);\n\n return e.summary?.trim() || words(e.event);\n}\n\n/** Who did it: an admin by name, or the business itself. */\nexport function historyBy(e: PosHistoryEntry): string {\n if (e.by.type === 'admin') return `${e.by.name ?? 'An admin'} · Admin`;\n\n return 'The business';\n}\n\n/** \"2 terminals\", \"1 terminal\". */\nexport const count = (n: number, one: string, many = `${one}s`) =>\n `${n} ${n === 1 ? one : many}`;\n\n/**\n * A period phrase after a noun: \"in the last 30 days\", \"in Sep 30 – Oct 1\",\n * and \"yet\" for all time (\"No payments yet\").\n */\nexport function inPeriod(phrase: string): string {\n if (phrase === 'all time') return 'yet';\n\n return `in ${phrase.startsWith('last') ? 'the ' : ''}${phrase}`;\n}\n\n/** Who may sign in at a terminal, in a line. */\nexport const accessLine = (t: Pick<PosTerminal, 'operatorAccess'>) =>\n t.operatorAccess === 'any_member'\n ? 'Anyone on the till roster can sign in'\n : 'Only the people listed can sign in';\n"],"mappings":"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","names":[]}
|
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
import {
|
|
3
3
|
keepPreviousData,
|
|
4
4
|
useMutation,
|
|
5
|
-
useInfiniteQuery,
|
|
6
5
|
useQuery,
|
|
7
6
|
useQueryClient
|
|
8
7
|
} from "@tanstack/react-query";
|
|
9
8
|
import { adminPosApi } from "@withpotter/lib/admin-pos-api";
|
|
10
|
-
import { isForbidden, isNotFound
|
|
9
|
+
import { isForbidden, isNotFound } from "./pos-rules";
|
|
11
10
|
const POS_KEY = ["platform-admin", "pos"];
|
|
12
11
|
const key = (...parts) => [...POS_KEY, ...parts];
|
|
13
12
|
const retry = (count, error) => !isForbidden(error) && !isNotFound(error) && count < 1;
|
|
@@ -53,67 +52,6 @@ function usePosShifts(filters, enabled = true) {
|
|
|
53
52
|
retry
|
|
54
53
|
});
|
|
55
54
|
}
|
|
56
|
-
function useTenantRoster(tenantId, enabled = true) {
|
|
57
|
-
return useQuery({
|
|
58
|
-
queryKey: key("roster", tenantId),
|
|
59
|
-
queryFn: () => adminPosApi.roster(tenantId),
|
|
60
|
-
enabled,
|
|
61
|
-
retry
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
function useTenantTillRequests(tenantId, enabled = true) {
|
|
65
|
-
return useQuery({
|
|
66
|
-
queryKey: key("till-requests", tenantId),
|
|
67
|
-
queryFn: () => adminPosApi.tillRequests(tenantId),
|
|
68
|
-
enabled,
|
|
69
|
-
retry
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
function usePosPayments(filters = {}, enabled = true) {
|
|
73
|
-
const { tenantId, status } = filters;
|
|
74
|
-
return useInfiniteQuery({
|
|
75
|
-
queryKey: key("payments", tenantId || "all", status || "any"),
|
|
76
|
-
queryFn: ({ pageParam }) => adminPosApi.payments({
|
|
77
|
-
tenantId: tenantId || void 0,
|
|
78
|
-
status: status || void 0,
|
|
79
|
-
cursor: pageParam,
|
|
80
|
-
limit: 25
|
|
81
|
-
}),
|
|
82
|
-
initialPageParam: void 0,
|
|
83
|
-
getNextPageParam: (last) => last.nextCursor ?? void 0,
|
|
84
|
-
enabled,
|
|
85
|
-
retry
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
const useTenantPayments = (tenantId, enabled = true) => usePosPayments({ tenantId }, enabled);
|
|
89
|
-
const oliveRetry = (count, error) => !isForbidden(error) && !isNotFound(error) && !isNotLinked(error) && count < 1;
|
|
90
|
-
function useOliveAccount(tenantId, enabled = true) {
|
|
91
|
-
return useQuery({
|
|
92
|
-
queryKey: key("olive", tenantId, "account"),
|
|
93
|
-
queryFn: () => adminPosApi.olive.account(tenantId),
|
|
94
|
-
enabled,
|
|
95
|
-
retry: oliveRetry,
|
|
96
|
-
staleTime: 6e4
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
function useOliveAccountTransactions(tenantId, enabled = true) {
|
|
100
|
-
return useQuery({
|
|
101
|
-
queryKey: key("olive", tenantId, "account-transactions"),
|
|
102
|
-
queryFn: () => adminPosApi.olive.accountTransactions(tenantId),
|
|
103
|
-
enabled,
|
|
104
|
-
retry: oliveRetry,
|
|
105
|
-
staleTime: 6e4
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
function useOliveOutlets(tenantId, enabled = true) {
|
|
109
|
-
return useQuery({
|
|
110
|
-
queryKey: key("olive", tenantId, "outlets"),
|
|
111
|
-
queryFn: () => adminPosApi.olive.outlets(tenantId),
|
|
112
|
-
enabled,
|
|
113
|
-
retry: oliveRetry,
|
|
114
|
-
staleTime: 6e4
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
55
|
function useTenantPos(tenantId, w, enabled = true) {
|
|
118
56
|
return useQuery({
|
|
119
57
|
queryKey: key("tenant", tenantId, w),
|
|
@@ -140,19 +78,12 @@ const useCloseShift = () => usePosWrite(
|
|
|
140
78
|
export {
|
|
141
79
|
POS_KEY,
|
|
142
80
|
useCloseShift,
|
|
143
|
-
useOliveAccount,
|
|
144
|
-
useOliveAccountTransactions,
|
|
145
|
-
useOliveOutlets,
|
|
146
81
|
usePosHistory,
|
|
147
|
-
usePosPayments,
|
|
148
82
|
usePosShifts,
|
|
149
83
|
usePosSummary,
|
|
150
84
|
usePosTerminal,
|
|
151
85
|
usePosTerminals,
|
|
152
|
-
useTenantPayments,
|
|
153
86
|
useTenantPos,
|
|
154
|
-
useTenantRoster,
|
|
155
|
-
useTenantTillRequests,
|
|
156
87
|
useTerminalStatus
|
|
157
88
|
};
|
|
158
89
|
//# sourceMappingURL=use-pos.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/_pos/use-pos.ts"],"sourcesContent":["'use client';\n\nimport {\n keepPreviousData,\n useMutation,\n
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/_pos/use-pos.ts"],"sourcesContent":["'use client';\n\nimport {\n keepPreviousData,\n useMutation,\n useQuery,\n useQueryClient,\n} from '@tanstack/react-query';\nimport { adminPosApi, type TerminalAct } from '@withpotter/lib/admin-pos-api';\nimport type {\n ShiftFilters,\n TerminalFilters,\n} from '@withpotter/lib/admin-pos-query';\nimport type { PosWindow } from '@withpotter/lib/admin-pos-types';\nimport { isForbidden, isNotFound } from './pos-rules';\n\n/** Every POS query sits under this, so one action refreshes them all. */\nexport const POS_KEY = ['platform-admin', 'pos'] as const;\n\nconst key = (...parts: unknown[]) => [...POS_KEY, ...parts];\n\n/** A 403 or 404 will not change on a retry; anything else gets one more go. */\nconst retry = (count: number, error: unknown) =>\n !isForbidden(error) && !isNotFound(error) && count < 1;\n\nexport function usePosSummary(w: PosWindow, enabled = true) {\n return useQuery({\n queryKey: key('summary', w),\n queryFn: () => adminPosApi.summary(w),\n enabled,\n retry,\n });\n}\n\nexport function usePosTerminals(filters: TerminalFilters, enabled = true) {\n return useQuery({\n queryKey: key('terminals', filters),\n queryFn: () => adminPosApi.terminals(filters),\n placeholderData: keepPreviousData,\n enabled,\n retry,\n });\n}\n\nexport function usePosTerminal(id: string, w: PosWindow, enabled = true) {\n return useQuery({\n queryKey: key('terminal', id, w),\n queryFn: () => adminPosApi.terminal(id, w),\n enabled,\n retry,\n });\n}\n\nexport function usePosHistory(id: string, enabled = true) {\n return useQuery({\n queryKey: key('history', id),\n queryFn: () => adminPosApi.history(id),\n enabled,\n retry,\n });\n}\n\nexport function usePosShifts(filters: ShiftFilters, enabled = true) {\n return useQuery({\n queryKey: key('shifts', filters),\n queryFn: () => adminPosApi.shifts(filters),\n placeholderData: keepPreviousData,\n enabled,\n retry,\n });\n}\n\nexport function useTenantPos(tenantId: string, w: PosWindow, enabled = true) {\n return useQuery({\n queryKey: key('tenant', tenantId, w),\n queryFn: () => adminPosApi.tenant(tenantId, w),\n enabled,\n retry,\n });\n}\n\n/**\n * Every action can move the figures, the lists and a business's tab, so\n * all of POS is refetched after it, a 409 included: that means someone\n * else acted first. The refetch is not awaited, so the caller's own\n * `onSuccess` (the toast, closing the form) is not held back by it.\n */\nfunction usePosWrite<V, R>(fn: (v: V) => Promise<R>) {\n const client = useQueryClient();\n\n return useMutation({\n mutationFn: fn,\n onSettled: () => {\n void client.invalidateQueries({ queryKey: POS_KEY });\n },\n });\n}\n\nexport const useTerminalStatus = () =>\n usePosWrite((v: { id: string; act: TerminalAct; reason: string }) =>\n adminPosApi.setTerminalStatus(v.id, v.act, v.reason),\n );\n\nexport const useCloseShift = () =>\n usePosWrite((v: { id: string; reason: string }) =>\n adminPosApi.closeShift(v.id, v.reason),\n );\n"],"mappings":";AAEA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,mBAAqC;AAM9C,SAAS,aAAa,kBAAkB;AAGjC,MAAM,UAAU,CAAC,kBAAkB,KAAK;AAE/C,MAAM,MAAM,IAAI,UAAqB,CAAC,GAAG,SAAS,GAAG,KAAK;AAG1D,MAAM,QAAQ,CAAC,OAAe,UAC5B,CAAC,YAAY,KAAK,KAAK,CAAC,WAAW,KAAK,KAAK,QAAQ;AAEhD,SAAS,cAAc,GAAc,UAAU,MAAM;AAC1D,SAAO,SAAS;AAAA,IACd,UAAU,IAAI,WAAW,CAAC;AAAA,IAC1B,SAAS,MAAM,YAAY,QAAQ,CAAC;AAAA,IACpC;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAEO,SAAS,gBAAgB,SAA0B,UAAU,MAAM;AACxE,SAAO,SAAS;AAAA,IACd,UAAU,IAAI,aAAa,OAAO;AAAA,IAClC,SAAS,MAAM,YAAY,UAAU,OAAO;AAAA,IAC5C,iBAAiB;AAAA,IACjB;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAEO,SAAS,eAAe,IAAY,GAAc,UAAU,MAAM;AACvE,SAAO,SAAS;AAAA,IACd,UAAU,IAAI,YAAY,IAAI,CAAC;AAAA,IAC/B,SAAS,MAAM,YAAY,SAAS,IAAI,CAAC;AAAA,IACzC;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAEO,SAAS,cAAc,IAAY,UAAU,MAAM;AACxD,SAAO,SAAS;AAAA,IACd,UAAU,IAAI,WAAW,EAAE;AAAA,IAC3B,SAAS,MAAM,YAAY,QAAQ,EAAE;AAAA,IACrC;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAEO,SAAS,aAAa,SAAuB,UAAU,MAAM;AAClE,SAAO,SAAS;AAAA,IACd,UAAU,IAAI,UAAU,OAAO;AAAA,IAC/B,SAAS,MAAM,YAAY,OAAO,OAAO;AAAA,IACzC,iBAAiB;AAAA,IACjB;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAEO,SAAS,aAAa,UAAkB,GAAc,UAAU,MAAM;AAC3E,SAAO,SAAS;AAAA,IACd,UAAU,IAAI,UAAU,UAAU,CAAC;AAAA,IACnC,SAAS,MAAM,YAAY,OAAO,UAAU,CAAC;AAAA,IAC7C;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAQA,SAAS,YAAkB,IAA0B;AACnD,QAAM,SAAS,eAAe;AAE9B,SAAO,YAAY;AAAA,IACjB,YAAY;AAAA,IACZ,WAAW,MAAM;AACf,WAAK,OAAO,kBAAkB,EAAE,UAAU,QAAQ,CAAC;AAAA,IACrD;AAAA,EACF,CAAC;AACH;AAEO,MAAM,oBAAoB,MAC/B;AAAA,EAAY,CAAC,MACX,YAAY,kBAAkB,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM;AACrD;AAEK,MAAM,gBAAgB,MAC3B;AAAA,EAAY,CAAC,MACX,YAAY,WAAW,EAAE,IAAI,EAAE,MAAM;AACvC;","names":[]}
|
|
@@ -39,10 +39,10 @@ const NAV_GROUPS = [
|
|
|
39
39
|
]
|
|
40
40
|
},
|
|
41
41
|
{
|
|
42
|
-
label: "
|
|
42
|
+
label: "Merchants",
|
|
43
43
|
items: [
|
|
44
|
-
{ href: "/admin/tenants", label: "
|
|
45
|
-
{ href: "/admin/accounts", label: "
|
|
44
|
+
{ href: "/admin/tenants", label: "Merchants", icon: Building2 },
|
|
45
|
+
{ href: "/admin/accounts", label: "People", icon: Users },
|
|
46
46
|
{ href: "/admin/sales", label: "Sales", icon: Target, module: "sales" },
|
|
47
47
|
{
|
|
48
48
|
href: "/admin/verification",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/_shell/nav-config.ts"],"sourcesContent":["import {\n LayoutGrid,\n Building2,\n Users,\n ShieldCheck,\n ScrollText,\n KeyRound,\n Coins,\n MessageSquareText,\n Headphones,\n Star,\n Settings,\n Target,\n Gauge,\n BadgeCheck,\n Mail,\n Landmark,\n ArrowDownToLine,\n Percent,\n MonitorSmartphone,\n Store,\n} from 'lucide-react';\nimport type { AdminModule } from './nav-modules';\n\nexport type NavItem = {\n href: string;\n label: string;\n icon: typeof LayoutGrid;\n superadmin?: boolean;\n permission?: string;\n activeRoutes?: string[];\n /** Which inbox count to show beside the label. */\n inbox?: 'support' | 'feedback';\n /** Listed only when the host has turned this module on. */\n module?: AdminModule;\n /** Listed only while this module is off: the entry stands in for a page that module would otherwise carry as a tab. */\n unlessModule?: AdminModule;\n};\n\nexport type NavGroup = { label: string | null; items: NavItem[] };\n\n/**\n * Pages a host adds to one of the groups below (a new label adds a group;\n * `null` is the top group with Overview). `replace` drops that group's Box\n * items first, so a host can fold a group into one entry of its own.\n
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/_shell/nav-config.ts"],"sourcesContent":["import {\n LayoutGrid,\n Building2,\n Users,\n ShieldCheck,\n ScrollText,\n KeyRound,\n Coins,\n MessageSquareText,\n Headphones,\n Star,\n Settings,\n Target,\n Gauge,\n BadgeCheck,\n Mail,\n Landmark,\n ArrowDownToLine,\n Percent,\n MonitorSmartphone,\n Store,\n} from 'lucide-react';\nimport type { AdminModule } from './nav-modules';\n\nexport type NavItem = {\n href: string;\n label: string;\n icon: typeof LayoutGrid;\n superadmin?: boolean;\n permission?: string;\n activeRoutes?: string[];\n /** Which inbox count to show beside the label. */\n inbox?: 'support' | 'feedback';\n /** Listed only when the host has turned this module on. */\n module?: AdminModule;\n /** Listed only while this module is off: the entry stands in for a page that module would otherwise carry as a tab. */\n unlessModule?: AdminModule;\n};\n\nexport type NavGroup = { label: string | null; items: NavItem[] };\n\n/**\n * Pages a host adds to one of the groups below (a new label adds a group;\n * `null` is the top group with Overview). `replace` drops that group's Box\n * items first, so a host can fold a group into one entry of its own.\n */\nexport type NavAddition = {\n group: string | null;\n items: NavItem[];\n replace?: boolean;\n};\n\n/**\n * Second-level tabs shown above the page while inside Product feedback.\n * Product insights is not one: no box serves it.\n */\nexport const PRODUCT_FEEDBACK_NAV: NavItem[] = [\n { href: '/admin/feedback', label: 'Feedback', icon: MessageSquareText },\n {\n href: '/admin/reviews',\n label: 'Reviews',\n icon: Star,\n permission: 'reviews.read',\n module: 'reviews',\n },\n];\n\n/**\n * Grouped like a banking control room: the merchant first, then money, then\n * operations, then the platform itself. Same order and words as the Box admin.\n */\nexport const NAV_GROUPS: NavGroup[] = [\n {\n label: null,\n items: [\n { href: '/admin', label: 'Overview', icon: LayoutGrid },\n { href: '/admin/revenue', label: 'Revenue', icon: Coins },\n ],\n },\n {\n label: 'Merchants',\n items: [\n { href: '/admin/tenants', label: 'Merchants', icon: Building2 },\n { href: '/admin/accounts', label: 'People', icon: Users },\n { href: '/admin/sales', label: 'Sales', icon: Target, module: 'sales' },\n {\n href: '/admin/verification',\n label: 'Verification',\n icon: BadgeCheck,\n permission: 'compliance.read',\n module: 'verification',\n },\n {\n href: '/admin/newsletters',\n label: 'Newsletters',\n icon: Mail,\n permission: 'newsletters.read',\n module: 'newsletters',\n },\n ],\n },\n {\n label: 'Money',\n items: [\n {\n href: '/admin/wallets',\n label: 'Wallets & ledger',\n icon: Landmark,\n module: 'wallets',\n },\n {\n href: '/admin/withdrawals',\n label: 'Withdrawals',\n icon: ArrowDownToLine,\n module: 'withdrawals',\n },\n {\n href: '/admin/pricing',\n label: 'Pricing',\n icon: Percent,\n module: 'pricing',\n },\n {\n href: '/admin/payout-tiers',\n label: 'Payout tiers',\n icon: Gauge,\n permission: 'money.payout_tiers.read',\n },\n ],\n },\n {\n label: 'Operations',\n items: [\n {\n href: '/admin/pos',\n label: 'POS',\n icon: MonitorSmartphone,\n permission: 'pos.read',\n module: 'pos',\n },\n {\n href: '/admin/commerce',\n label: 'Commerce',\n icon: Store,\n module: 'commerce',\n },\n ],\n },\n {\n label: 'Inbox',\n items: [\n {\n href: '/admin/support',\n label: 'Support',\n icon: Headphones,\n permission: 'support.read',\n inbox: 'support',\n module: 'support',\n },\n {\n href: '/admin/feedback',\n label: 'Product feedback',\n icon: MessageSquareText,\n activeRoutes: PRODUCT_FEEDBACK_NAV.map(({ href }) => href),\n inbox: 'feedback',\n module: 'feedback',\n },\n {\n href: '/admin/reviews',\n label: 'Reviews',\n icon: Star,\n permission: 'reviews.read',\n module: 'reviews',\n unlessModule: 'feedback',\n },\n ],\n },\n {\n label: 'Platform',\n items: [\n {\n href: '/admin/admins',\n label: 'Admins',\n icon: ShieldCheck,\n superadmin: true,\n },\n {\n href: '/admin/roles',\n label: 'Roles',\n icon: KeyRound,\n superadmin: true,\n },\n { href: '/admin/audit', label: 'Audit log', icon: ScrollText },\n {\n href: '/admin/settings',\n label: 'Settings',\n icon: Settings,\n module: 'settings',\n },\n ],\n },\n];\n"],"mappings":"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","names":[]}
|
|
@@ -12,13 +12,11 @@ const canAccess = (session) => (item) => {
|
|
|
12
12
|
};
|
|
13
13
|
function withAdditions(groups, additions = []) {
|
|
14
14
|
const merged = groups.map((g) => ({ ...g, items: [...g.items] }));
|
|
15
|
-
additions.forEach(({ group, items, replace
|
|
15
|
+
additions.forEach(({ group, items, replace }) => {
|
|
16
16
|
const found = merged.find((g) => g.label === group);
|
|
17
17
|
if (found) {
|
|
18
18
|
if (replace) found.items = [];
|
|
19
|
-
|
|
20
|
-
if (at2 === -1) found.items.push(...items);
|
|
21
|
-
else found.items.splice(at2 + 1, 0, ...items);
|
|
19
|
+
found.items.push(...items);
|
|
22
20
|
return;
|
|
23
21
|
}
|
|
24
22
|
const platform = merged.findIndex((g) => g.label === "Platform");
|