@withpotter/pages-admin 0.13.0-next.123 → 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.
Files changed (262) hide show
  1. package/dist/admin/_money/cash-postings.js +3 -20
  2. package/dist/admin/_money/cash-postings.js.map +1 -1
  3. package/dist/admin/_money/cash.module.css +0 -10
  4. package/dist/admin/_money/finder-format.js +0 -1
  5. package/dist/admin/_money/finder-format.js.map +1 -1
  6. package/dist/admin/_money/format.js +0 -1
  7. package/dist/admin/_money/format.js.map +1 -1
  8. package/dist/admin/_overview/activation-panel.js +6 -6
  9. package/dist/admin/_overview/activation-panel.js.map +1 -1
  10. package/dist/admin/_overview/attention-groups.js +8 -8
  11. package/dist/admin/_overview/attention-groups.js.map +1 -1
  12. package/dist/admin/_overview/attention-panel.js +33 -37
  13. package/dist/admin/_overview/attention-panel.js.map +1 -1
  14. package/dist/admin/_overview/attention.module.css +3 -0
  15. package/dist/admin/_overview/control-room.js +13 -41
  16. package/dist/admin/_overview/control-room.js.map +1 -1
  17. package/dist/admin/_overview/kpi-row.js +7 -7
  18. package/dist/admin/_overview/kpi-row.js.map +1 -1
  19. package/dist/admin/_overview/leaders-panel.js +1 -1
  20. package/dist/admin/_overview/leaders-panel.js.map +1 -1
  21. package/dist/admin/_overview/overdue-outstanding.js +1 -2
  22. package/dist/admin/_overview/overdue-outstanding.js.map +1 -1
  23. package/dist/admin/_overview/portfolio-panel.js +5 -5
  24. package/dist/admin/_overview/portfolio-panel.js.map +1 -1
  25. package/dist/admin/_overview/position-panel.js +12 -8
  26. package/dist/admin/_overview/position-panel.js.map +1 -1
  27. package/dist/admin/_pos/pos-figures.js +5 -18
  28. package/dist/admin/_pos/pos-figures.js.map +1 -1
  29. package/dist/admin/_pos/pos-rules.js +0 -2
  30. package/dist/admin/_pos/pos-rules.js.map +1 -1
  31. package/dist/admin/_pos/pos-words.js +1 -6
  32. package/dist/admin/_pos/pos-words.js.map +1 -1
  33. package/dist/admin/_pos/use-pos.js +1 -70
  34. package/dist/admin/_pos/use-pos.js.map +1 -1
  35. package/dist/admin/_shell/nav-config.js +3 -3
  36. package/dist/admin/_shell/nav-config.js.map +1 -1
  37. package/dist/admin/_shell/nav-filter.js +2 -4
  38. package/dist/admin/_shell/nav-filter.js.map +1 -1
  39. package/dist/admin/_shell/nav-row.js +2 -5
  40. package/dist/admin/_shell/nav-row.js.map +1 -1
  41. package/dist/admin/_shell/sidebar-footer.js +0 -20
  42. package/dist/admin/_shell/sidebar-footer.js.map +1 -1
  43. package/dist/admin/_shell/sidebar-footer.module.css +0 -17
  44. package/dist/admin/_shell/sidebar.js +15 -25
  45. package/dist/admin/_shell/sidebar.js.map +1 -1
  46. package/dist/admin/_shell/sidebar.module.css +0 -69
  47. package/dist/admin/_shell/use-fluid-hover.js +6 -37
  48. package/dist/admin/_shell/use-fluid-hover.js.map +1 -1
  49. package/dist/admin/_ui/admin.css +0 -7
  50. package/dist/admin/_ui/data-table.js +1 -1
  51. package/dist/admin/_ui/data-table.js.map +1 -1
  52. package/dist/admin/_ui/list-card.js +1 -1
  53. package/dist/admin/_ui/list-card.js.map +1 -1
  54. package/dist/admin/_ui/list-card.module.css +16 -2
  55. package/dist/admin/_ui/money-view.js +0 -1
  56. package/dist/admin/_ui/money-view.js.map +1 -1
  57. package/dist/admin/_ui/page-header.js +4 -31
  58. package/dist/admin/_ui/page-header.js.map +1 -1
  59. package/dist/admin/_ui/panel.module.css +10 -0
  60. package/dist/admin/_ui/stat-card.js +2 -11
  61. package/dist/admin/_ui/stat-card.js.map +1 -1
  62. package/dist/admin/_ui/trend.js +2 -2
  63. package/dist/admin/_ui/trend.js.map +1 -1
  64. package/dist/admin/_ui/workspace-tab.js +1 -11
  65. package/dist/admin/_ui/workspace-tab.js.map +1 -1
  66. package/dist/admin/accounts/account-panel.js +6 -49
  67. package/dist/admin/accounts/account-panel.js.map +1 -1
  68. package/dist/admin/accounts/account-panel.module.css +0 -73
  69. package/dist/admin/accounts/account-row.js +2 -11
  70. package/dist/admin/accounts/account-row.js.map +1 -1
  71. package/dist/admin/accounts/account-vitals.js +13 -15
  72. package/dist/admin/accounts/account-vitals.js.map +1 -1
  73. package/dist/admin/accounts/accounts-table.js +8 -39
  74. package/dist/admin/accounts/accounts-table.js.map +1 -1
  75. package/dist/admin/accounts/accounts-workspace.js +2 -2
  76. package/dist/admin/accounts/accounts-workspace.js.map +1 -1
  77. package/dist/admin/accounts/owner-session.js +6 -0
  78. package/dist/admin/accounts/owner-session.js.map +1 -1
  79. package/dist/admin/accounts/rows.module.css +0 -17
  80. package/dist/admin/audit/status-pill.js +0 -6
  81. package/dist/admin/audit/status-pill.js.map +1 -1
  82. package/dist/admin/audit/status-pill.module.css +0 -4
  83. package/dist/admin/commerce/commerce-overview.js +2 -2
  84. package/dist/admin/commerce/commerce-overview.js.map +1 -1
  85. package/dist/admin/commerce/merchant-meta.js +0 -1
  86. package/dist/admin/commerce/merchant-meta.js.map +1 -1
  87. package/dist/admin/commerce/merchant-view.js +2 -2
  88. package/dist/admin/commerce/merchant-view.js.map +1 -1
  89. package/dist/admin/feedback/prompt-operations.module.css +2 -0
  90. package/dist/admin/feedback/prompt-preview.module.css +2 -0
  91. package/dist/admin/hooks.js +1 -57
  92. package/dist/admin/hooks.js.map +1 -1
  93. package/dist/admin/layout.js +2 -8
  94. package/dist/admin/layout.js.map +1 -1
  95. package/dist/admin/page.js +8 -28
  96. package/dist/admin/page.js.map +1 -1
  97. package/dist/admin/payout-tiers/_tier-dialog.js +21 -31
  98. package/dist/admin/payout-tiers/_tier-dialog.js.map +1 -1
  99. package/dist/admin/payout-tiers/tier-preview-result.js +9 -10
  100. package/dist/admin/payout-tiers/tier-preview-result.js.map +1 -1
  101. package/dist/admin/payout-tiers/tiers-table.js +17 -40
  102. package/dist/admin/payout-tiers/tiers-table.js.map +1 -1
  103. package/dist/admin/payout-tiers/tiers-workspace.js +17 -20
  104. package/dist/admin/payout-tiers/tiers-workspace.js.map +1 -1
  105. package/dist/admin/pos/pos-workspace.js +2 -6
  106. package/dist/admin/pos/pos-workspace.js.map +1 -1
  107. package/dist/admin/pos/shifts-tab.js +9 -16
  108. package/dist/admin/pos/shifts-tab.js.map +1 -1
  109. package/dist/admin/revenue/cohort-panel.js +6 -6
  110. package/dist/admin/revenue/cohort-panel.js.map +1 -1
  111. package/dist/admin/revenue/health-panel.js +21 -22
  112. package/dist/admin/revenue/health-panel.js.map +1 -1
  113. package/dist/admin/revenue/plan-mix-panel.js +3 -3
  114. package/dist/admin/revenue/plan-mix-panel.js.map +1 -1
  115. package/dist/admin/revenue/quality-panel.js +19 -19
  116. package/dist/admin/revenue/quality-panel.js.map +1 -1
  117. package/dist/admin/revenue/revenue-kpis.js +50 -111
  118. package/dist/admin/revenue/revenue-kpis.js.map +1 -1
  119. package/dist/admin/revenue/revenue-overview.js +46 -39
  120. package/dist/admin/revenue/revenue-overview.js.map +1 -1
  121. package/dist/admin/revenue/revenue-retention.js +13 -17
  122. package/dist/admin/revenue/revenue-retention.js.map +1 -1
  123. package/dist/admin/revenue/revenue-workspace.js +6 -53
  124. package/dist/admin/revenue/revenue-workspace.js.map +1 -1
  125. package/dist/admin/revenue/subscriptions-panel.js +14 -14
  126. package/dist/admin/revenue/subscriptions-panel.js.map +1 -1
  127. package/dist/admin/revenue/top-tenants-panel.js +4 -4
  128. package/dist/admin/revenue/top-tenants-panel.js.map +1 -1
  129. package/dist/admin/settings/account-tab.js +25 -1
  130. package/dist/admin/settings/account-tab.js.map +1 -1
  131. package/dist/admin/settings/general-tab.js +2 -2
  132. package/dist/admin/settings/general-tab.js.map +1 -1
  133. package/dist/admin/settings/system-status-card.js +30 -0
  134. package/dist/admin/settings/system-status-card.js.map +1 -0
  135. package/dist/admin/support/queue-card.module.css +4 -1
  136. package/dist/admin/tenants/[id]/commerce/format.js +1 -2
  137. package/dist/admin/tenants/[id]/commerce/format.js.map +1 -1
  138. package/dist/admin/tenants/[id]/commerce/recent-orders.js +1 -1
  139. package/dist/admin/tenants/[id]/commerce/recent-orders.js.map +1 -1
  140. package/dist/admin/tenants/[id]/page.js +10 -37
  141. package/dist/admin/tenants/[id]/page.js.map +1 -1
  142. package/dist/admin/tenants/[id]/tenant-layout.module.css +4 -1
  143. package/dist/admin/tenants/[id]/tenant-overview.js +2 -3
  144. package/dist/admin/tenants/[id]/tenant-overview.js.map +1 -1
  145. package/dist/admin/tenants/[id]/tenant-overview.module.css +1 -1
  146. package/dist/admin/tenants/[id]/tenant-pos.js +16 -64
  147. package/dist/admin/tenants/[id]/tenant-pos.js.map +1 -1
  148. package/dist/admin/tenants/[id]/tenant-wallet-card.js +2 -3
  149. package/dist/admin/tenants/[id]/tenant-wallet-card.js.map +1 -1
  150. package/dist/admin/tenants/[id]/verification/document-viewer.js +52 -3
  151. package/dist/admin/tenants/[id]/verification/document-viewer.js.map +1 -1
  152. package/dist/admin/tenants/[id]/verification/document-viewer.module.css +0 -51
  153. package/dist/admin/tenants/[id]/verification/tenant-verification.js +0 -10
  154. package/dist/admin/tenants/[id]/verification/tenant-verification.js.map +1 -1
  155. package/dist/admin/tenants/tenant-list-row.js +1 -2
  156. package/dist/admin/tenants/tenant-list-row.js.map +1 -1
  157. package/dist/admin/tenants/tenants-table.js +2 -3
  158. package/dist/admin/tenants/tenants-table.js.map +1 -1
  159. package/dist/admin/tenants/tenants-workspace.js +2 -2
  160. package/dist/admin/tenants/tenants-workspace.js.map +1 -1
  161. package/dist/admin/verification/verification-page.js +3 -19
  162. package/dist/admin/verification/verification-page.js.map +1 -1
  163. package/dist/admin/verification/verification.module.css +0 -10
  164. package/dist/admin/verification/waiting-case-actions.js +68 -0
  165. package/dist/admin/verification/waiting-case-actions.js.map +1 -0
  166. package/dist/admin/verification/waiting-cases.js +48 -66
  167. package/dist/admin/verification/waiting-cases.js.map +1 -1
  168. package/package.json +5 -5
  169. package/dist/admin/_pos/olive-balance.js +0 -40
  170. package/dist/admin/_pos/olive-balance.js.map +0 -1
  171. package/dist/admin/_pos/olive-state.js +0 -23
  172. package/dist/admin/_pos/olive-state.js.map +0 -1
  173. package/dist/admin/_pos/payments-model.js +0 -55
  174. package/dist/admin/_pos/payments-model.js.map +0 -1
  175. package/dist/admin/_pos/tenant-operators.js +0 -63
  176. package/dist/admin/_pos/tenant-operators.js.map +0 -1
  177. package/dist/admin/_pos/tenant-outlets.js +0 -77
  178. package/dist/admin/_pos/tenant-outlets.js.map +0 -1
  179. package/dist/admin/_pos/tenant-pos-figures.js +0 -82
  180. package/dist/admin/_pos/tenant-pos-figures.js.map +0 -1
  181. package/dist/admin/_pos/tenant-requests.js +0 -64
  182. package/dist/admin/_pos/tenant-requests.js.map +0 -1
  183. package/dist/admin/_pos/tenant-transactions.js +0 -145
  184. package/dist/admin/_pos/tenant-transactions.js.map +0 -1
  185. package/dist/admin/_shell/nav-fold.js +0 -14
  186. package/dist/admin/_shell/nav-fold.js.map +0 -1
  187. package/dist/admin/_shell/nav-group.js +0 -78
  188. package/dist/admin/_shell/nav-group.js.map +0 -1
  189. package/dist/admin/_shell/use-open-groups.js +0 -64
  190. package/dist/admin/_shell/use-open-groups.js.map +0 -1
  191. package/dist/admin/_ui/page-header.module.css +0 -45
  192. package/dist/admin/accounts/account-sessions.js +0 -70
  193. package/dist/admin/accounts/account-sessions.js.map +0 -1
  194. package/dist/admin/accounts/account-sessions.module.css +0 -55
  195. package/dist/admin/accounts/account-stage-filter.js +0 -29
  196. package/dist/admin/accounts/account-stage-filter.js.map +0 -1
  197. package/dist/admin/accounts/account-stage.js +0 -81
  198. package/dist/admin/accounts/account-stage.js.map +0 -1
  199. package/dist/admin/accounts/hint.js +0 -12
  200. package/dist/admin/accounts/hint.js.map +0 -1
  201. package/dist/admin/payout-tiers/requirement-labels.js +0 -70
  202. package/dist/admin/payout-tiers/requirement-labels.js.map +0 -1
  203. package/dist/admin/pos/transactions-tab.js +0 -214
  204. package/dist/admin/pos/transactions-tab.js.map +0 -1
  205. package/dist/admin/revenue/channel-panel.js +0 -49
  206. package/dist/admin/revenue/channel-panel.js.map +0 -1
  207. package/dist/admin/revenue/channel-rows.js +0 -17
  208. package/dist/admin/revenue/channel-rows.js.map +0 -1
  209. package/dist/admin/revenue/download-button.js +0 -40
  210. package/dist/admin/revenue/download-button.js.map +0 -1
  211. package/dist/admin/revenue/earnings/earnings-colors.js +0 -25
  212. package/dist/admin/revenue/earnings/earnings-colors.js.map +0 -1
  213. package/dist/admin/revenue/earnings/earnings-legend.js +0 -75
  214. package/dist/admin/revenue/earnings/earnings-legend.js.map +0 -1
  215. package/dist/admin/revenue/earnings/earnings-summary.js +0 -296
  216. package/dist/admin/revenue/earnings/earnings-summary.js.map +0 -1
  217. package/dist/admin/revenue/earnings/earnings-table.js +0 -143
  218. package/dist/admin/revenue/earnings/earnings-table.js.map +0 -1
  219. package/dist/admin/revenue/earnings/earnings.module.css +0 -201
  220. package/dist/admin/revenue/earnings/use-earnings.js +0 -27
  221. package/dist/admin/revenue/earnings/use-earnings.js.map +0 -1
  222. package/dist/admin/revenue/ledger-model.js +0 -104
  223. package/dist/admin/revenue/ledger-model.js.map +0 -1
  224. package/dist/admin/revenue/ledger.module.css +0 -144
  225. package/dist/admin/revenue/merchant-headline.js +0 -37
  226. package/dist/admin/revenue/merchant-headline.js.map +0 -1
  227. package/dist/admin/revenue/merchant-payments-view.js +0 -234
  228. package/dist/admin/revenue/merchant-payments-view.js.map +0 -1
  229. package/dist/admin/revenue/method-cards.js +0 -40
  230. package/dist/admin/revenue/method-cards.js.map +0 -1
  231. package/dist/admin/revenue/payment-drawer.js +0 -53
  232. package/dist/admin/revenue/payment-drawer.js.map +0 -1
  233. package/dist/admin/revenue/plan-lines.js +0 -36
  234. package/dist/admin/revenue/plan-lines.js.map +0 -1
  235. package/dist/admin/revenue/plan-payment-drawer.js +0 -42
  236. package/dist/admin/revenue/plan-payment-drawer.js.map +0 -1
  237. package/dist/admin/revenue/plans-panel.js +0 -48
  238. package/dist/admin/revenue/plans-panel.js.map +0 -1
  239. package/dist/admin/revenue/platform-fees.js +0 -9
  240. package/dist/admin/revenue/platform-fees.js.map +0 -1
  241. package/dist/admin/revenue/print-summary-button.js +0 -27
  242. package/dist/admin/revenue/print-summary-button.js.map +0 -1
  243. package/dist/admin/revenue/subscriptions-view.js +0 -213
  244. package/dist/admin/revenue/subscriptions-view.js.map +0 -1
  245. package/dist/admin/revenue/summary-download.js +0 -38
  246. package/dist/admin/revenue/summary-download.js.map +0 -1
  247. package/dist/admin/revenue/summary-print.js +0 -82
  248. package/dist/admin/revenue/summary-print.js.map +0 -1
  249. package/dist/admin/revenue/summary-rows.js +0 -44
  250. package/dist/admin/revenue/summary-rows.js.map +0 -1
  251. package/dist/admin/revenue/url-filters.js +0 -36
  252. package/dist/admin/revenue/url-filters.js.map +0 -1
  253. package/dist/admin/revenue/use-url-filters.js +0 -24
  254. package/dist/admin/revenue/use-url-filters.js.map +0 -1
  255. package/dist/admin/tenants/[id]/use-tenant-wallet.js +0 -23
  256. package/dist/admin/tenants/[id]/use-tenant-wallet.js.map +0 -1
  257. package/dist/admin/tenants/[id]/verification/document-file.js +0 -78
  258. package/dist/admin/tenants/[id]/verification/document-file.js.map +0 -1
  259. package/dist/admin/tenants/[id]/verification/sent-files.js +0 -33
  260. package/dist/admin/tenants/[id]/verification/sent-files.js.map +0 -1
  261. package/dist/admin/verification/preview-dialog.js +0 -111
  262. package/dist/admin/verification/preview-dialog.js.map +0 -1
@@ -2,7 +2,6 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useState } from "react";
4
4
  import { Button } from "@withpotter/ui-kit/button";
5
- import { FilterSelect, Filters } from "../_ui";
6
5
  import { useMoneyPermissions } from "./permissions";
7
6
  import { ProposeCashDialog } from "./propose-dialog";
8
7
  import { ReviewProposalDialog } from "./review-dialog";
@@ -25,13 +24,8 @@ function ActionCard({
25
24
  ] })
26
25
  ] });
27
26
  }
28
- const SHOW = [
29
- { value: "post", label: "Cash postings" },
30
- { value: "waiting", label: "Waiting for approval" }
31
- ];
32
27
  function CashPostings() {
33
28
  const perms = useMoneyPermissions();
34
- const [show, setShow] = useState(SHOW[0].value);
35
29
  const [proposing, setProposing] = useState(false);
36
30
  const [reviewing, setReviewing] = useState(false);
37
31
  const [review, setReview] = useState({ id: "", nonce: 0 });
@@ -40,18 +34,7 @@ function CashPostings() {
40
34
  setReviewing(true);
41
35
  };
42
36
  return /* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: 16 }, children: [
43
- /* @__PURE__ */ jsx("div", { className: styles.show, children: /* @__PURE__ */ jsx(Filters, { label: "Choose what to show", children: /* @__PURE__ */ jsx(
44
- FilterSelect,
45
- {
46
- label: "Show",
47
- value: show,
48
- allValue: SHOW[0].value,
49
- onChange: setShow,
50
- options: SHOW,
51
- className: styles.showSelect
52
- }
53
- ) }) }),
54
- show === "post" && /* @__PURE__ */ jsxs("div", { className: styles.cashGrid, children: [
37
+ /* @__PURE__ */ jsxs("div", { className: styles.cashGrid, children: [
55
38
  /* @__PURE__ */ jsx(
56
39
  ActionCard,
57
40
  {
@@ -67,14 +50,14 @@ function CashPostings() {
67
50
  ActionCard,
68
51
  {
69
52
  title: "Review by ID",
70
- body: "Open a posting by its ID when it is not in Waiting for approval.",
53
+ body: "Open a posting by its ID when it is not in the list below.",
71
54
  cta: "Review by ID",
72
55
  hint: perms.approveHint,
73
56
  onClick: () => openReview("")
74
57
  }
75
58
  )
76
59
  ] }),
77
- show === "waiting" && /* @__PURE__ */ jsx(WaitingProposals, { onReview: openReview }),
60
+ /* @__PURE__ */ jsx(WaitingProposals, { onReview: openReview }),
78
61
  /* @__PURE__ */ jsx(ProposeCashDialog, { open: proposing, onClose: () => setProposing(false) }),
79
62
  /* @__PURE__ */ jsx(
80
63
  ReviewProposalDialog,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/_money/cash-postings.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { Button } from '@withpotter/ui-kit/button';\nimport { FilterSelect, Filters } from '../_ui';\nimport { useMoneyPermissions } from './permissions';\nimport { ProposeCashDialog } from './propose-dialog';\nimport { ReviewProposalDialog } from './review-dialog';\nimport { WaitingProposals } from './waiting-proposals';\nimport styles from './cash.module.css';\n\nfunction ActionCard({\n title,\n body,\n cta,\n hint,\n primary,\n onClick,\n}: {\n title: string;\n body: string;\n cta: string;\n /** Set when this admin may lack the permission; the API decides. */\n hint?: string;\n primary?: boolean;\n onClick: () => void;\n}) {\n return (\n <div className={styles.actionCard}>\n <h3 className={styles.actionTitle}>{title}</h3>\n <p className={styles.actionBody}>{body}</p>\n <div className={styles.actionFoot}>\n <Button variant={primary ? 'primary' : 'secondary'} onClick={onClick}>\n {cta}\n </Button>\n {hint && <span className=\"adm-caption\">{hint}</span>}\n </div>\n </div>\n );\n}\n\nconst SHOW = [\n { value: 'post', label: 'Cash postings' },\n { value: 'waiting', label: 'Waiting for approval' },\n];\n\n/**\n * Maker-checker cash postings: one admin proposes, a different admin\n * approves or rejects it from the waiting list (or by its ID). One part at a\n * time, chosen from a dropdown on the right.\n */\nexport function CashPostings() {\n const perms = useMoneyPermissions();\n const [show, setShow] = useState(SHOW[0].value);\n const [proposing, setProposing] = useState(false);\n const [reviewing, setReviewing] = useState(false);\n // A fresh dialog per opening, so a pasted ID never leaks into the next one.\n const [review, setReview] = useState({ id: '', nonce: 0 });\n const openReview = (id: string) => {\n setReview((r) => ({ id, nonce: r.nonce + 1 }));\n setReviewing(true);\n };\n\n return (\n <div style={{ display: 'grid', gap: 16 }}>\n <div className={styles.show}>\n <Filters label=\"Choose what to show\">\n <FilterSelect\n label=\"Show\"\n value={show}\n allValue={SHOW[0].value}\n onChange={setShow}\n options={SHOW}\n className={styles.showSelect}\n />\n </Filters>\n </div>\n {show === 'post' && (\n <div className={styles.cashGrid}>\n <ActionCard\n primary\n title=\"Propose a cash posting\"\n body=\"Book opening cash, or cash a merchant handed over off-platform. Nothing moves until a second admin approves.\"\n cta=\"Propose cash posting\"\n hint={perms.proposeHint}\n onClick={() => setProposing(true)}\n />\n <ActionCard\n title=\"Review by ID\"\n body=\"Open a posting by its ID when it is not in Waiting for approval.\"\n cta=\"Review by ID\"\n hint={perms.approveHint}\n onClick={() => openReview('')}\n />\n </div>\n )}\n {show === 'waiting' && <WaitingProposals onReview={openReview} />}\n <ProposeCashDialog open={proposing} onClose={() => setProposing(false)} />\n <ReviewProposalDialog\n key={review.nonce}\n open={reviewing}\n initialId={review.id}\n onClose={() => setReviewing(false)}\n />\n </div>\n );\n}\n"],"mappings":";AA6BM,cAEA,YAFA;AA3BN,SAAS,gBAAgB;AACzB,SAAS,cAAc;AACvB,SAAS,cAAc,eAAe;AACtC,SAAS,2BAA2B;AACpC,SAAS,yBAAyB;AAClC,SAAS,4BAA4B;AACrC,SAAS,wBAAwB;AACjC,OAAO,YAAY;AAEnB,SAAS,WAAW;AAAA,EAClB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAQG;AACD,SACE,qBAAC,SAAI,WAAW,OAAO,YACrB;AAAA,wBAAC,QAAG,WAAW,OAAO,aAAc,iBAAM;AAAA,IAC1C,oBAAC,OAAE,WAAW,OAAO,YAAa,gBAAK;AAAA,IACvC,qBAAC,SAAI,WAAW,OAAO,YACrB;AAAA,0BAAC,UAAO,SAAS,UAAU,YAAY,aAAa,SACjD,eACH;AAAA,MACC,QAAQ,oBAAC,UAAK,WAAU,eAAe,gBAAK;AAAA,OAC/C;AAAA,KACF;AAEJ;AAEA,MAAM,OAAO;AAAA,EACX,EAAE,OAAO,QAAQ,OAAO,gBAAgB;AAAA,EACxC,EAAE,OAAO,WAAW,OAAO,uBAAuB;AACpD;AAOO,SAAS,eAAe;AAC7B,QAAM,QAAQ,oBAAoB;AAClC,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK,CAAC,EAAE,KAAK;AAC9C,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,KAAK;AAChD,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,KAAK;AAEhD,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,EAAE,IAAI,IAAI,OAAO,EAAE,CAAC;AACzD,QAAM,aAAa,CAAC,OAAe;AACjC,cAAU,CAAC,OAAO,EAAE,IAAI,OAAO,EAAE,QAAQ,EAAE,EAAE;AAC7C,iBAAa,IAAI;AAAA,EACnB;AAEA,SACE,qBAAC,SAAI,OAAO,EAAE,SAAS,QAAQ,KAAK,GAAG,GACrC;AAAA,wBAAC,SAAI,WAAW,OAAO,MACrB,8BAAC,WAAQ,OAAM,uBACb;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,OAAO;AAAA,QACP,UAAU,KAAK,CAAC,EAAE;AAAA,QAClB,UAAU;AAAA,QACV,SAAS;AAAA,QACT,WAAW,OAAO;AAAA;AAAA,IACpB,GACF,GACF;AAAA,IACC,SAAS,UACR,qBAAC,SAAI,WAAW,OAAO,UACrB;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,SAAO;AAAA,UACP,OAAM;AAAA,UACN,MAAK;AAAA,UACL,KAAI;AAAA,UACJ,MAAM,MAAM;AAAA,UACZ,SAAS,MAAM,aAAa,IAAI;AAAA;AAAA,MAClC;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,OAAM;AAAA,UACN,MAAK;AAAA,UACL,KAAI;AAAA,UACJ,MAAM,MAAM;AAAA,UACZ,SAAS,MAAM,WAAW,EAAE;AAAA;AAAA,MAC9B;AAAA,OACF;AAAA,IAED,SAAS,aAAa,oBAAC,oBAAiB,UAAU,YAAY;AAAA,IAC/D,oBAAC,qBAAkB,MAAM,WAAW,SAAS,MAAM,aAAa,KAAK,GAAG;AAAA,IACxE;AAAA,MAAC;AAAA;AAAA,QAEC,MAAM;AAAA,QACN,WAAW,OAAO;AAAA,QAClB,SAAS,MAAM,aAAa,KAAK;AAAA;AAAA,MAH5B,OAAO;AAAA,IAId;AAAA,KACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/_money/cash-postings.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { Button } from '@withpotter/ui-kit/button';\nimport { useMoneyPermissions } from './permissions';\nimport { ProposeCashDialog } from './propose-dialog';\nimport { ReviewProposalDialog } from './review-dialog';\nimport { WaitingProposals } from './waiting-proposals';\nimport styles from './cash.module.css';\n\nfunction ActionCard({\n title,\n body,\n cta,\n hint,\n primary,\n onClick,\n}: {\n title: string;\n body: string;\n cta: string;\n /** Set when this admin may lack the permission; the API decides. */\n hint?: string;\n primary?: boolean;\n onClick: () => void;\n}) {\n return (\n <div className={styles.actionCard}>\n <h3 className={styles.actionTitle}>{title}</h3>\n <p className={styles.actionBody}>{body}</p>\n <div className={styles.actionFoot}>\n <Button variant={primary ? 'primary' : 'secondary'} onClick={onClick}>\n {cta}\n </Button>\n {hint && <span className=\"adm-caption\">{hint}</span>}\n </div>\n </div>\n );\n}\n\n/**\n * Maker-checker cash postings: one admin proposes, a different admin\n * approves or rejects it from the waiting list (or by its ID).\n */\nexport function CashPostings() {\n const perms = useMoneyPermissions();\n const [proposing, setProposing] = useState(false);\n const [reviewing, setReviewing] = useState(false);\n // A fresh dialog per opening, so a pasted ID never leaks into the next one.\n const [review, setReview] = useState({ id: '', nonce: 0 });\n const openReview = (id: string) => {\n setReview((r) => ({ id, nonce: r.nonce + 1 }));\n setReviewing(true);\n };\n\n return (\n <div style={{ display: 'grid', gap: 16 }}>\n <div className={styles.cashGrid}>\n <ActionCard\n primary\n title=\"Propose a cash posting\"\n body=\"Book opening cash, or cash a merchant handed over off-platform. Nothing moves until a second admin approves.\"\n cta=\"Propose cash posting\"\n hint={perms.proposeHint}\n onClick={() => setProposing(true)}\n />\n <ActionCard\n title=\"Review by ID\"\n body=\"Open a posting by its ID when it is not in the list below.\"\n cta=\"Review by ID\"\n hint={perms.approveHint}\n onClick={() => openReview('')}\n />\n </div>\n <WaitingProposals onReview={openReview} />\n <ProposeCashDialog open={proposing} onClose={() => setProposing(false)} />\n <ReviewProposalDialog\n key={review.nonce}\n open={reviewing}\n initialId={review.id}\n onClose={() => setReviewing(false)}\n />\n </div>\n );\n}\n"],"mappings":";AA4BM,cAEA,YAFA;AA1BN,SAAS,gBAAgB;AACzB,SAAS,cAAc;AACvB,SAAS,2BAA2B;AACpC,SAAS,yBAAyB;AAClC,SAAS,4BAA4B;AACrC,SAAS,wBAAwB;AACjC,OAAO,YAAY;AAEnB,SAAS,WAAW;AAAA,EAClB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAQG;AACD,SACE,qBAAC,SAAI,WAAW,OAAO,YACrB;AAAA,wBAAC,QAAG,WAAW,OAAO,aAAc,iBAAM;AAAA,IAC1C,oBAAC,OAAE,WAAW,OAAO,YAAa,gBAAK;AAAA,IACvC,qBAAC,SAAI,WAAW,OAAO,YACrB;AAAA,0BAAC,UAAO,SAAS,UAAU,YAAY,aAAa,SACjD,eACH;AAAA,MACC,QAAQ,oBAAC,UAAK,WAAU,eAAe,gBAAK;AAAA,OAC/C;AAAA,KACF;AAEJ;AAMO,SAAS,eAAe;AAC7B,QAAM,QAAQ,oBAAoB;AAClC,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,KAAK;AAChD,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,KAAK;AAEhD,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,EAAE,IAAI,IAAI,OAAO,EAAE,CAAC;AACzD,QAAM,aAAa,CAAC,OAAe;AACjC,cAAU,CAAC,OAAO,EAAE,IAAI,OAAO,EAAE,QAAQ,EAAE,EAAE;AAC7C,iBAAa,IAAI;AAAA,EACnB;AAEA,SACE,qBAAC,SAAI,OAAO,EAAE,SAAS,QAAQ,KAAK,GAAG,GACrC;AAAA,yBAAC,SAAI,WAAW,OAAO,UACrB;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,SAAO;AAAA,UACP,OAAM;AAAA,UACN,MAAK;AAAA,UACL,KAAI;AAAA,UACJ,MAAM,MAAM;AAAA,UACZ,SAAS,MAAM,aAAa,IAAI;AAAA;AAAA,MAClC;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,OAAM;AAAA,UACN,MAAK;AAAA,UACL,KAAI;AAAA,UACJ,MAAM,MAAM;AAAA,UACZ,SAAS,MAAM,WAAW,EAAE;AAAA;AAAA,MAC9B;AAAA,OACF;AAAA,IACA,oBAAC,oBAAiB,UAAU,YAAY;AAAA,IACxC,oBAAC,qBAAkB,MAAM,WAAW,SAAS,MAAM,aAAa,KAAK,GAAG;AAAA,IACxE;AAAA,MAAC;AAAA;AAAA,QAEC,MAAM;AAAA,QACN,WAAW,OAAO;AAAA,QAClB,SAAS,MAAM,aAAa,KAAK;AAAA;AAAA,MAH5B,OAAO;AAAA,IAId;AAAA,KACF;AAEJ;","names":[]}
@@ -116,13 +116,3 @@
116
116
  margin: 0;
117
117
  padding: 16px 20px 24px;
118
118
  }
119
-
120
- /* Which part shows: the dropdown on the right. */
121
- .show {
122
- display: flex;
123
- justify-content: flex-end;
124
- }
125
-
126
- .show .showSelect {
127
- width: 220px;
128
- }
@@ -9,7 +9,6 @@ function compactAmount(amount, currency) {
9
9
  return new Intl.NumberFormat("en-US", {
10
10
  ...base,
11
11
  style: "currency",
12
- currencyDisplay: "narrowSymbol",
13
12
  currency
14
13
  }).format(amount);
15
14
  } catch {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/_money/finder-format.ts"],"sourcesContent":["/** Formatting for the merchant finder and its quick view. */\n\n/**\n * The merchant's currency when the row carries one, flat or on the profile.\n * The tenants list does not send it today, so callers fall back to plain.\n */\nexport function currencyOf(row: object): string | null {\n const r = row as {\n currency?: string | null;\n profile?: { currency?: string | null } | null;\n };\n\n return r.currency ?? r.profile?.currency ?? null;\n}\n\n/** A short figure: \"₦1.2M\" in a currency, \"1.2M\" without one. */\nexport function compactAmount(amount: number, currency: string | null): string {\n const base = { notation: 'compact', maximumFractionDigits: 1 } as const;\n\n if (currency) {\n try {\n return new Intl.NumberFormat('en-US', {\n ...base,\n style: 'currency',\n currencyDisplay: 'narrowSymbol',\n currency,\n }).format(amount);\n } catch {\n // An unknown code falls through to the plain number.\n }\n }\n\n return new Intl.NumberFormat('en-US', base).format(amount ?? 0);\n}\n\n/** An account number cut to its last four: \"•••• 4821\". */\nexport function maskAccount(accountNumber: string): string {\n const last = accountNumber.replace(/\\s/g, '').slice(-4);\n\n return last ? `•••• ${last}` : '–';\n}\n\n/** True when a minor-unit string holds something other than zero. */\nexport function nonZero(minor: string): boolean {\n try {\n return BigInt(minor) !== BigInt(0);\n } catch {\n return false;\n }\n}\n\n/** The wallet ID when the tenant detail sends one (it is not typed yet). */\nexport function walletIdOf(wallet: object | null | undefined): string | null {\n const id = (wallet as { id?: unknown } | null | undefined)?.id;\n\n return typeof id === 'string' && id ? id : null;\n}\n"],"mappings":"AAMO,SAAS,WAAW,KAA4B;AACrD,QAAM,IAAI;AAKV,SAAO,EAAE,YAAY,EAAE,SAAS,YAAY;AAC9C;AAGO,SAAS,cAAc,QAAgB,UAAiC;AAC7E,QAAM,OAAO,EAAE,UAAU,WAAW,uBAAuB,EAAE;AAE7D,MAAI,UAAU;AACZ,QAAI;AACF,aAAO,IAAI,KAAK,aAAa,SAAS;AAAA,QACpC,GAAG;AAAA,QACH,OAAO;AAAA,QACP,iBAAiB;AAAA,QACjB;AAAA,MACF,CAAC,EAAE,OAAO,MAAM;AAAA,IAClB,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,SAAO,IAAI,KAAK,aAAa,SAAS,IAAI,EAAE,OAAO,UAAU,CAAC;AAChE;AAGO,SAAS,YAAY,eAA+B;AACzD,QAAM,OAAO,cAAc,QAAQ,OAAO,EAAE,EAAE,MAAM,EAAE;AAEtD,SAAO,OAAO,4BAAQ,IAAI,KAAK;AACjC;AAGO,SAAS,QAAQ,OAAwB;AAC9C,MAAI;AACF,WAAO,OAAO,KAAK,MAAM,OAAO,CAAC;AAAA,EACnC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGO,SAAS,WAAW,QAAkD;AAC3E,QAAM,KAAM,QAAgD;AAE5D,SAAO,OAAO,OAAO,YAAY,KAAK,KAAK;AAC7C;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/_money/finder-format.ts"],"sourcesContent":["/** Formatting for the merchant finder and its quick view. */\n\n/**\n * The merchant's currency when the row carries one, flat or on the profile.\n * The tenants list does not send it today, so callers fall back to plain.\n */\nexport function currencyOf(row: object): string | null {\n const r = row as {\n currency?: string | null;\n profile?: { currency?: string | null } | null;\n };\n\n return r.currency ?? r.profile?.currency ?? null;\n}\n\n/** A short figure: \"₦1.2M\" in a currency, \"1.2M\" without one. */\nexport function compactAmount(amount: number, currency: string | null): string {\n const base = { notation: 'compact', maximumFractionDigits: 1 } as const;\n\n if (currency) {\n try {\n return new Intl.NumberFormat('en-US', {\n ...base,\n style: 'currency',\n currency,\n }).format(amount);\n } catch {\n // An unknown code falls through to the plain number.\n }\n }\n\n return new Intl.NumberFormat('en-US', base).format(amount ?? 0);\n}\n\n/** An account number cut to its last four: \"•••• 4821\". */\nexport function maskAccount(accountNumber: string): string {\n const last = accountNumber.replace(/\\s/g, '').slice(-4);\n\n return last ? `•••• ${last}` : '–';\n}\n\n/** True when a minor-unit string holds something other than zero. */\nexport function nonZero(minor: string): boolean {\n try {\n return BigInt(minor) !== BigInt(0);\n } catch {\n return false;\n }\n}\n\n/** The wallet ID when the tenant detail sends one (it is not typed yet). */\nexport function walletIdOf(wallet: object | null | undefined): string | null {\n const id = (wallet as { id?: unknown } | null | undefined)?.id;\n\n return typeof id === 'string' && id ? id : null;\n}\n"],"mappings":"AAMO,SAAS,WAAW,KAA4B;AACrD,QAAM,IAAI;AAKV,SAAO,EAAE,YAAY,EAAE,SAAS,YAAY;AAC9C;AAGO,SAAS,cAAc,QAAgB,UAAiC;AAC7E,QAAM,OAAO,EAAE,UAAU,WAAW,uBAAuB,EAAE;AAE7D,MAAI,UAAU;AACZ,QAAI;AACF,aAAO,IAAI,KAAK,aAAa,SAAS;AAAA,QACpC,GAAG;AAAA,QACH,OAAO;AAAA,QACP;AAAA,MACF,CAAC,EAAE,OAAO,MAAM;AAAA,IAClB,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,SAAO,IAAI,KAAK,aAAa,SAAS,IAAI,EAAE,OAAO,UAAU,CAAC;AAChE;AAGO,SAAS,YAAY,eAA+B;AACzD,QAAM,OAAO,cAAc,QAAQ,OAAO,EAAE,EAAE,MAAM,EAAE;AAEtD,SAAO,OAAO,4BAAQ,IAAI,KAAK;AACjC;AAGO,SAAS,QAAQ,OAAwB;AAC9C,MAAI;AACF,WAAO,OAAO,KAAK,MAAM,OAAO,CAAC;AAAA,EACnC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGO,SAAS,WAAW,QAAkD;AAC3E,QAAM,KAAM,QAAgD;AAE5D,SAAO,OAAO,OAAO,YAAY,KAAK,KAAK;AAC7C;","names":[]}
@@ -3,7 +3,6 @@ function money(amount, currency) {
3
3
  try {
4
4
  return new Intl.NumberFormat("en-US", {
5
5
  style: "currency",
6
- currencyDisplay: "narrowSymbol",
7
6
  currency: currency || "NGN",
8
7
  minimumFractionDigits: 2,
9
8
  maximumFractionDigits: 2
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/_money/format.ts"],"sourcesContent":["import { formatMinor } from '@withpotter/lib/money-minor';\n\n/** Money and time formatting for the money screens. Amounts are major units. */\n\nexport function money(amount: number, currency: string): string {\n try {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currencyDisplay: 'narrowSymbol',\n currency: currency || 'NGN',\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n }).format(amount ?? 0);\n } catch {\n return `${currency} ${(amount ?? 0).toFixed(2)}`;\n }\n}\n\n/**\n * Minor units held as a string, shown in the currency (\"150000\" NGN → ₦1,500.00,\n * \"1500\" JPY → ¥1,500). Split at the currency's own exponent, never ÷ 100.\n */\nexport function moneyMinor(minor: string, currency: string): string {\n return /^-?\\d+$/.test(minor)\n ? formatMinor(minor, currency || 'NGN')\n : `${minor} ${currency}`;\n}\n\nexport function when(iso: string | null | undefined): string {\n if (!iso) return '–';\n\n const d = new Date(iso);\n\n if (Number.isNaN(d.getTime())) return '–';\n\n return d.toLocaleString(undefined, {\n day: 'numeric',\n month: 'short',\n hour: '2-digit',\n minute: '2-digit',\n });\n}\n\nexport function day(iso: string | null | undefined): string {\n if (!iso) return '–';\n\n const d = new Date(iso);\n\n if (Number.isNaN(d.getTime())) return '–';\n\n // \"15 Oct 2026\" everywhere, whatever the browser's locale.\n return d.toLocaleDateString('en-GB', {\n day: 'numeric',\n month: 'short',\n year: 'numeric',\n });\n}\n\n/** \"3h\", \"2d\": how long a record has been open. */\nexport function age(iso: string, now = Date.now()): string {\n const ms = Math.max(0, now - new Date(iso).getTime());\n const h = Math.floor(ms / 3_600_000);\n\n if (h < 1) return `${Math.max(1, Math.floor(ms / 60_000))}m`;\n\n if (h < 48) return `${h}h`;\n\n return `${Math.floor(h / 24)}d`;\n}\n\n/** Keep the start and end of a long ID: \"8f2c1a…9b0d\". */\nexport function middle(id: string, keep = 6): string {\n return id.length <= keep * 2 + 1\n ? id\n : `${id.slice(0, keep)}…${id.slice(-4)}`;\n}\n\n/** An API error's own words when it has some, a plain line when not. */\nexport function errorText(error: unknown, fallback: string): string {\n if (error instanceof Error && error.message) return error.message;\n\n return fallback;\n}\n"],"mappings":"AAAA,SAAS,mBAAmB;AAIrB,SAAS,MAAM,QAAgB,UAA0B;AAC9D,MAAI;AACF,WAAO,IAAI,KAAK,aAAa,SAAS;AAAA,MACpC,OAAO;AAAA,MACP,iBAAiB;AAAA,MACjB,UAAU,YAAY;AAAA,MACtB,uBAAuB;AAAA,MACvB,uBAAuB;AAAA,IACzB,CAAC,EAAE,OAAO,UAAU,CAAC;AAAA,EACvB,QAAQ;AACN,WAAO,GAAG,QAAQ,KAAK,UAAU,GAAG,QAAQ,CAAC,CAAC;AAAA,EAChD;AACF;AAMO,SAAS,WAAW,OAAe,UAA0B;AAClE,SAAO,UAAU,KAAK,KAAK,IACvB,YAAY,OAAO,YAAY,KAAK,IACpC,GAAG,KAAK,IAAI,QAAQ;AAC1B;AAEO,SAAS,KAAK,KAAwC;AAC3D,MAAI,CAAC,IAAK,QAAO;AAEjB,QAAM,IAAI,IAAI,KAAK,GAAG;AAEtB,MAAI,OAAO,MAAM,EAAE,QAAQ,CAAC,EAAG,QAAO;AAEtC,SAAO,EAAE,eAAe,QAAW;AAAA,IACjC,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,EACV,CAAC;AACH;AAEO,SAAS,IAAI,KAAwC;AAC1D,MAAI,CAAC,IAAK,QAAO;AAEjB,QAAM,IAAI,IAAI,KAAK,GAAG;AAEtB,MAAI,OAAO,MAAM,EAAE,QAAQ,CAAC,EAAG,QAAO;AAGtC,SAAO,EAAE,mBAAmB,SAAS;AAAA,IACnC,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,EACR,CAAC;AACH;AAGO,SAAS,IAAI,KAAa,MAAM,KAAK,IAAI,GAAW;AACzD,QAAM,KAAK,KAAK,IAAI,GAAG,MAAM,IAAI,KAAK,GAAG,EAAE,QAAQ,CAAC;AACpD,QAAM,IAAI,KAAK,MAAM,KAAK,IAAS;AAEnC,MAAI,IAAI,EAAG,QAAO,GAAG,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,GAAM,CAAC,CAAC;AAEzD,MAAI,IAAI,GAAI,QAAO,GAAG,CAAC;AAEvB,SAAO,GAAG,KAAK,MAAM,IAAI,EAAE,CAAC;AAC9B;AAGO,SAAS,OAAO,IAAY,OAAO,GAAW;AACnD,SAAO,GAAG,UAAU,OAAO,IAAI,IAC3B,KACA,GAAG,GAAG,MAAM,GAAG,IAAI,CAAC,SAAI,GAAG,MAAM,EAAE,CAAC;AAC1C;AAGO,SAAS,UAAU,OAAgB,UAA0B;AAClE,MAAI,iBAAiB,SAAS,MAAM,QAAS,QAAO,MAAM;AAE1D,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/_money/format.ts"],"sourcesContent":["import { formatMinor } from '@withpotter/lib/money-minor';\n\n/** Money and time formatting for the money screens. Amounts are major units. */\n\nexport function money(amount: number, currency: string): string {\n try {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: currency || 'NGN',\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n }).format(amount ?? 0);\n } catch {\n return `${currency} ${(amount ?? 0).toFixed(2)}`;\n }\n}\n\n/**\n * Minor units held as a string, shown in the currency (\"150000\" NGN → ₦1,500.00,\n * \"1500\" JPY → ¥1,500). Split at the currency's own exponent, never ÷ 100.\n */\nexport function moneyMinor(minor: string, currency: string): string {\n return /^-?\\d+$/.test(minor)\n ? formatMinor(minor, currency || 'NGN')\n : `${minor} ${currency}`;\n}\n\nexport function when(iso: string | null | undefined): string {\n if (!iso) return '–';\n\n const d = new Date(iso);\n\n if (Number.isNaN(d.getTime())) return '–';\n\n return d.toLocaleString(undefined, {\n day: 'numeric',\n month: 'short',\n hour: '2-digit',\n minute: '2-digit',\n });\n}\n\nexport function day(iso: string | null | undefined): string {\n if (!iso) return '–';\n\n const d = new Date(iso);\n\n if (Number.isNaN(d.getTime())) return '–';\n\n // \"15 Oct 2026\" everywhere, whatever the browser's locale.\n return d.toLocaleDateString('en-GB', {\n day: 'numeric',\n month: 'short',\n year: 'numeric',\n });\n}\n\n/** \"3h\", \"2d\": how long a record has been open. */\nexport function age(iso: string, now = Date.now()): string {\n const ms = Math.max(0, now - new Date(iso).getTime());\n const h = Math.floor(ms / 3_600_000);\n\n if (h < 1) return `${Math.max(1, Math.floor(ms / 60_000))}m`;\n\n if (h < 48) return `${h}h`;\n\n return `${Math.floor(h / 24)}d`;\n}\n\n/** Keep the start and end of a long ID: \"8f2c1a…9b0d\". */\nexport function middle(id: string, keep = 6): string {\n return id.length <= keep * 2 + 1\n ? id\n : `${id.slice(0, keep)}…${id.slice(-4)}`;\n}\n\n/** An API error's own words when it has some, a plain line when not. */\nexport function errorText(error: unknown, fallback: string): string {\n if (error instanceof Error && error.message) return error.message;\n\n return fallback;\n}\n"],"mappings":"AAAA,SAAS,mBAAmB;AAIrB,SAAS,MAAM,QAAgB,UAA0B;AAC9D,MAAI;AACF,WAAO,IAAI,KAAK,aAAa,SAAS;AAAA,MACpC,OAAO;AAAA,MACP,UAAU,YAAY;AAAA,MACtB,uBAAuB;AAAA,MACvB,uBAAuB;AAAA,IACzB,CAAC,EAAE,OAAO,UAAU,CAAC;AAAA,EACvB,QAAQ;AACN,WAAO,GAAG,QAAQ,KAAK,UAAU,GAAG,QAAQ,CAAC,CAAC;AAAA,EAChD;AACF;AAMO,SAAS,WAAW,OAAe,UAA0B;AAClE,SAAO,UAAU,KAAK,KAAK,IACvB,YAAY,OAAO,YAAY,KAAK,IACpC,GAAG,KAAK,IAAI,QAAQ;AAC1B;AAEO,SAAS,KAAK,KAAwC;AAC3D,MAAI,CAAC,IAAK,QAAO;AAEjB,QAAM,IAAI,IAAI,KAAK,GAAG;AAEtB,MAAI,OAAO,MAAM,EAAE,QAAQ,CAAC,EAAG,QAAO;AAEtC,SAAO,EAAE,eAAe,QAAW;AAAA,IACjC,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,EACV,CAAC;AACH;AAEO,SAAS,IAAI,KAAwC;AAC1D,MAAI,CAAC,IAAK,QAAO;AAEjB,QAAM,IAAI,IAAI,KAAK,GAAG;AAEtB,MAAI,OAAO,MAAM,EAAE,QAAQ,CAAC,EAAG,QAAO;AAGtC,SAAO,EAAE,mBAAmB,SAAS;AAAA,IACnC,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,EACR,CAAC;AACH;AAGO,SAAS,IAAI,KAAa,MAAM,KAAK,IAAI,GAAW;AACzD,QAAM,KAAK,KAAK,IAAI,GAAG,MAAM,IAAI,KAAK,GAAG,EAAE,QAAQ,CAAC;AACpD,QAAM,IAAI,KAAK,MAAM,KAAK,IAAS;AAEnC,MAAI,IAAI,EAAG,QAAO,GAAG,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,GAAM,CAAC,CAAC;AAEzD,MAAI,IAAI,GAAI,QAAO,GAAG,CAAC;AAEvB,SAAO,GAAG,KAAK,MAAM,IAAI,EAAE,CAAC;AAC9B;AAGO,SAAS,OAAO,IAAY,OAAO,GAAW;AACnD,SAAO,GAAG,UAAU,OAAO,IAAI,IAC3B,KACA,GAAG,GAAG,MAAM,GAAG,IAAI,CAAC,SAAI,GAAG,MAAM,EAAE,CAAC;AAC1C;AAGO,SAAS,UAAU,OAAgB,UAA0B;AAClE,MAAI,iBAAiB,SAAS,MAAM,QAAS,QAAO,MAAM;AAE1D,SAAO;AACT;","names":[]}
@@ -6,11 +6,11 @@ function ActivationPanel({
6
6
  activation
7
7
  }) {
8
8
  if (!activation) {
9
- return /* @__PURE__ */ jsx(Panel, { title: "From sign-up to first sale", caption: "All time", height: "auto", children: /* @__PURE__ */ jsx(AdmLoading, { label: "Loading sign-up progress\u2026", size: "sm" }) });
9
+ return /* @__PURE__ */ jsx(Panel, { title: "Activation funnel", caption: "All time", height: "auto", children: /* @__PURE__ */ jsx(AdmLoading, { label: "Loading activation funnel\u2026", size: "sm" }) });
10
10
  }
11
11
  const stages = activation.funnel;
12
12
  const top = stages[0]?.count || 1;
13
- return /* @__PURE__ */ jsxs(Panel, { title: "From sign-up to first sale", caption: "All time", height: "auto", children: [
13
+ return /* @__PURE__ */ jsxs(Panel, { title: "Activation funnel", caption: "All time", height: "auto", children: [
14
14
  /* @__PURE__ */ jsx("ul", { className: styles.bars, children: stages.map((s) => /* @__PURE__ */ jsxs("li", { children: [
15
15
  /* @__PURE__ */ jsxs("div", { className: styles.barLabel, children: [
16
16
  /* @__PURE__ */ jsx("span", { children: s.stage }),
@@ -26,24 +26,24 @@ function ActivationPanel({
26
26
  }
27
27
  ) })
28
28
  ] }, s.stage)) }),
29
- /* @__PURE__ */ jsx("div", { className: styles.subhead, children: "How many carry on" }),
29
+ /* @__PURE__ */ jsx("div", { className: styles.subhead, children: "Conversion" }),
30
30
  /* @__PURE__ */ jsxs("ul", { className: styles.bars, children: [
31
31
  /* @__PURE__ */ jsxs("li", { className: styles.barLabel, children: [
32
- /* @__PURE__ */ jsx("span", { children: "Signed up, then became active" }),
32
+ /* @__PURE__ */ jsx("span", { children: "Signed up \u2192 activated" }),
33
33
  /* @__PURE__ */ jsxs("strong", { children: [
34
34
  activation.activationRatePct,
35
35
  "%"
36
36
  ] })
37
37
  ] }),
38
38
  /* @__PURE__ */ jsxs("li", { className: styles.barLabel, children: [
39
- /* @__PURE__ */ jsx("span", { children: "Signed up, then took a first order" }),
39
+ /* @__PURE__ */ jsx("span", { children: "Signed up \u2192 first order" }),
40
40
  /* @__PURE__ */ jsxs("strong", { children: [
41
41
  activation.orderConversionPct,
42
42
  "%"
43
43
  ] })
44
44
  ] }),
45
45
  /* @__PURE__ */ jsxs("li", { className: styles.barLabel, children: [
46
- /* @__PURE__ */ jsx("span", { children: "Typical days to a first order" }),
46
+ /* @__PURE__ */ jsx("span", { children: "Median days to first order" }),
47
47
  /* @__PURE__ */ jsx("strong", { children: activation.medianDaysToFirstOrder == null ? "\u2013" : activation.medianDaysToFirstOrder })
48
48
  ] })
49
49
  ] })
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/_overview/activation-panel.tsx"],"sourcesContent":["import type { PlatformCockpit } from '@withpotter/lib/admin-api';\nimport { AdmLoading, Panel } from '../_ui';\nimport styles from './figures.module.css';\n\nconst integer = new Intl.NumberFormat();\n\n/**\n * Signed up → Activated → First order → First payout, lifetime (not\n * range-scoped — a funnel this shallow needs the whole base to read).\n */\nexport function ActivationPanel({\n activation,\n}: {\n activation: PlatformCockpit['activation'];\n}) {\n if (!activation) {\n return (\n <Panel title=\"From sign-up to first sale\" caption=\"All time\" height=\"auto\">\n <AdmLoading label=\"Loading sign-up progress…\" size=\"sm\" />\n </Panel>\n );\n }\n\n const stages = activation.funnel;\n const top = stages[0]?.count || 1;\n\n return (\n <Panel title=\"From sign-up to first sale\" caption=\"All time\" height=\"auto\">\n <ul className={styles.bars}>\n {stages.map((s) => (\n <li key={s.stage}>\n <div className={styles.barLabel}>\n <span>{s.stage}</span>\n <strong>{integer.format(s.count)}</strong>\n </div>\n <div className={styles.track}>\n <span\n style={{\n width: `${Math.max((s.count / top) * 100, 2)}%`,\n background: 'var(--accent-primary)',\n }}\n />\n </div>\n </li>\n ))}\n </ul>\n\n <div className={styles.subhead}>How many carry on</div>\n <ul className={styles.bars}>\n <li className={styles.barLabel}>\n <span>Signed up, then became active</span>\n <strong>{activation.activationRatePct}%</strong>\n </li>\n <li className={styles.barLabel}>\n <span>Signed up, then took a first order</span>\n <strong>{activation.orderConversionPct}%</strong>\n </li>\n <li className={styles.barLabel}>\n <span>Typical days to a first order</span>\n <strong>\n {activation.medianDaysToFirstOrder == null\n ? '–'\n : activation.medianDaysToFirstOrder}\n </strong>\n </li>\n </ul>\n </Panel>\n );\n}\n"],"mappings":"AAkBQ,cAaI,YAbJ;AAjBR,SAAS,YAAY,aAAa;AAClC,OAAO,YAAY;AAEnB,MAAM,UAAU,IAAI,KAAK,aAAa;AAM/B,SAAS,gBAAgB;AAAA,EAC9B;AACF,GAEG;AACD,MAAI,CAAC,YAAY;AACf,WACE,oBAAC,SAAM,OAAM,8BAA6B,SAAQ,YAAW,QAAO,QAClE,8BAAC,cAAW,OAAM,kCAA4B,MAAK,MAAK,GAC1D;AAAA,EAEJ;AAEA,QAAM,SAAS,WAAW;AAC1B,QAAM,MAAM,OAAO,CAAC,GAAG,SAAS;AAEhC,SACE,qBAAC,SAAM,OAAM,8BAA6B,SAAQ,YAAW,QAAO,QAClE;AAAA,wBAAC,QAAG,WAAW,OAAO,MACnB,iBAAO,IAAI,CAAC,MACX,qBAAC,QACC;AAAA,2BAAC,SAAI,WAAW,OAAO,UACrB;AAAA,4BAAC,UAAM,YAAE,OAAM;AAAA,QACf,oBAAC,YAAQ,kBAAQ,OAAO,EAAE,KAAK,GAAE;AAAA,SACnC;AAAA,MACA,oBAAC,SAAI,WAAW,OAAO,OACrB;AAAA,QAAC;AAAA;AAAA,UACC,OAAO;AAAA,YACL,OAAO,GAAG,KAAK,IAAK,EAAE,QAAQ,MAAO,KAAK,CAAC,CAAC;AAAA,YAC5C,YAAY;AAAA,UACd;AAAA;AAAA,MACF,GACF;AAAA,SAZO,EAAE,KAaX,CACD,GACH;AAAA,IAEA,oBAAC,SAAI,WAAW,OAAO,SAAS,+BAAiB;AAAA,IACjD,qBAAC,QAAG,WAAW,OAAO,MACpB;AAAA,2BAAC,QAAG,WAAW,OAAO,UACpB;AAAA,4BAAC,UAAK,2CAA6B;AAAA,QACnC,qBAAC,YAAQ;AAAA,qBAAW;AAAA,UAAkB;AAAA,WAAC;AAAA,SACzC;AAAA,MACA,qBAAC,QAAG,WAAW,OAAO,UACpB;AAAA,4BAAC,UAAK,gDAAkC;AAAA,QACxC,qBAAC,YAAQ;AAAA,qBAAW;AAAA,UAAmB;AAAA,WAAC;AAAA,SAC1C;AAAA,MACA,qBAAC,QAAG,WAAW,OAAO,UACpB;AAAA,4BAAC,UAAK,2CAA6B;AAAA,QACnC,oBAAC,YACE,qBAAW,0BAA0B,OAClC,WACA,WAAW,wBACjB;AAAA,SACF;AAAA,OACF;AAAA,KACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/_overview/activation-panel.tsx"],"sourcesContent":["import type { PlatformCockpit } from '@withpotter/lib/admin-api';\nimport { AdmLoading, Panel } from '../_ui';\nimport styles from './figures.module.css';\n\nconst integer = new Intl.NumberFormat();\n\n/**\n * Signed up → Activated → First order → First payout, lifetime (not\n * range-scoped — a funnel this shallow needs the whole base to read).\n */\nexport function ActivationPanel({\n activation,\n}: {\n activation: PlatformCockpit['activation'];\n}) {\n if (!activation) {\n return (\n <Panel title=\"Activation funnel\" caption=\"All time\" height=\"auto\">\n <AdmLoading label=\"Loading activation funnel…\" size=\"sm\" />\n </Panel>\n );\n }\n\n const stages = activation.funnel;\n const top = stages[0]?.count || 1;\n\n return (\n <Panel title=\"Activation funnel\" caption=\"All time\" height=\"auto\">\n <ul className={styles.bars}>\n {stages.map((s) => (\n <li key={s.stage}>\n <div className={styles.barLabel}>\n <span>{s.stage}</span>\n <strong>{integer.format(s.count)}</strong>\n </div>\n <div className={styles.track}>\n <span\n style={{\n width: `${Math.max((s.count / top) * 100, 2)}%`,\n background: 'var(--accent-primary)',\n }}\n />\n </div>\n </li>\n ))}\n </ul>\n\n <div className={styles.subhead}>Conversion</div>\n <ul className={styles.bars}>\n <li className={styles.barLabel}>\n <span>Signed up → activated</span>\n <strong>{activation.activationRatePct}%</strong>\n </li>\n <li className={styles.barLabel}>\n <span>Signed up → first order</span>\n <strong>{activation.orderConversionPct}%</strong>\n </li>\n <li className={styles.barLabel}>\n <span>Median days to first order</span>\n <strong>\n {activation.medianDaysToFirstOrder == null\n ? '–'\n : activation.medianDaysToFirstOrder}\n </strong>\n </li>\n </ul>\n </Panel>\n );\n}\n"],"mappings":"AAkBQ,cAaI,YAbJ;AAjBR,SAAS,YAAY,aAAa;AAClC,OAAO,YAAY;AAEnB,MAAM,UAAU,IAAI,KAAK,aAAa;AAM/B,SAAS,gBAAgB;AAAA,EAC9B;AACF,GAEG;AACD,MAAI,CAAC,YAAY;AACf,WACE,oBAAC,SAAM,OAAM,qBAAoB,SAAQ,YAAW,QAAO,QACzD,8BAAC,cAAW,OAAM,mCAA6B,MAAK,MAAK,GAC3D;AAAA,EAEJ;AAEA,QAAM,SAAS,WAAW;AAC1B,QAAM,MAAM,OAAO,CAAC,GAAG,SAAS;AAEhC,SACE,qBAAC,SAAM,OAAM,qBAAoB,SAAQ,YAAW,QAAO,QACzD;AAAA,wBAAC,QAAG,WAAW,OAAO,MACnB,iBAAO,IAAI,CAAC,MACX,qBAAC,QACC;AAAA,2BAAC,SAAI,WAAW,OAAO,UACrB;AAAA,4BAAC,UAAM,YAAE,OAAM;AAAA,QACf,oBAAC,YAAQ,kBAAQ,OAAO,EAAE,KAAK,GAAE;AAAA,SACnC;AAAA,MACA,oBAAC,SAAI,WAAW,OAAO,OACrB;AAAA,QAAC;AAAA;AAAA,UACC,OAAO;AAAA,YACL,OAAO,GAAG,KAAK,IAAK,EAAE,QAAQ,MAAO,KAAK,CAAC,CAAC;AAAA,YAC5C,YAAY;AAAA,UACd;AAAA;AAAA,MACF,GACF;AAAA,SAZO,EAAE,KAaX,CACD,GACH;AAAA,IAEA,oBAAC,SAAI,WAAW,OAAO,SAAS,wBAAU;AAAA,IAC1C,qBAAC,QAAG,WAAW,OAAO,MACpB;AAAA,2BAAC,QAAG,WAAW,OAAO,UACpB;AAAA,4BAAC,UAAK,wCAAqB;AAAA,QAC3B,qBAAC,YAAQ;AAAA,qBAAW;AAAA,UAAkB;AAAA,WAAC;AAAA,SACzC;AAAA,MACA,qBAAC,QAAG,WAAW,OAAO,UACpB;AAAA,4BAAC,UAAK,0CAAuB;AAAA,QAC7B,qBAAC,YAAQ;AAAA,qBAAW;AAAA,UAAmB;AAAA,WAAC;AAAA,SAC1C;AAAA,MACA,qBAAC,QAAG,WAAW,OAAO,UACpB;AAAA,4BAAC,UAAK,wCAA0B;AAAA,QAChC,oBAAC,YACE,qBAAW,0BAA0B,OAClC,WACA,WAAW,wBACjB;AAAA,SACF;AAAA,OACF;AAAA,KACF;AAEJ;","names":[]}
@@ -13,13 +13,13 @@ function attentionGroups(a) {
13
13
  tenantId: s.tenantId,
14
14
  href: tenant(s.tenantId),
15
15
  title: s.tenantName,
16
- detail: "Payout failed",
16
+ detail: "Settlement failed",
17
17
  value: inCurrency(s.netAmount, s.currency)
18
18
  }))
19
19
  },
20
20
  {
21
21
  key: "invoices",
22
- title: "Overdue bills",
22
+ title: "Overdue",
23
23
  count: a.overdueInvoices.count,
24
24
  note: overdueOutstanding(a.overdueInvoices) ?? void 0,
25
25
  items: a.overdueInvoices.sample.map((i) => ({
@@ -28,13 +28,13 @@ function attentionGroups(a) {
28
28
  tenantId: i.tenantId,
29
29
  href: tenant(i.tenantId),
30
30
  title: i.tenantName,
31
- detail: `Bill ${i.invoiceNumber}`,
31
+ detail: `Invoice ${i.invoiceNumber}`,
32
32
  value: inCurrency(i.amountDue, i.currency)
33
33
  }))
34
34
  },
35
35
  {
36
36
  key: "review",
37
- title: "Merchants to review",
37
+ title: "Review",
38
38
  count: a.needsReview.length,
39
39
  items: a.needsReview.map((t) => ({
40
40
  id: t.id,
@@ -43,12 +43,12 @@ function attentionGroups(a) {
43
43
  href: tenant(t.id),
44
44
  title: t.businessName,
45
45
  detail: t.slug,
46
- value: t.status.replace(/_/g, " ")
46
+ value: t.status
47
47
  }))
48
48
  },
49
49
  {
50
50
  key: "trials",
51
- title: "Free trials ending",
51
+ title: "Trials ending",
52
52
  count: a.trialsEndingSoon.length,
53
53
  items: a.trialsEndingSoon.map((t) => ({
54
54
  id: t.id,
@@ -57,12 +57,12 @@ function attentionGroups(a) {
57
57
  href: tenant(t.id),
58
58
  title: t.businessName,
59
59
  detail: t.slug,
60
- value: t.daysLeft === 0 ? "Today" : `${t.daysLeft} ${t.daysLeft === 1 ? "day" : "days"} left`
60
+ value: t.daysLeft === 0 ? "Today" : `${t.daysLeft}d left`
61
61
  }))
62
62
  },
63
63
  {
64
64
  key: "dormant",
65
- title: "Inactive",
65
+ title: "Dormant",
66
66
  count: a.dormantTenants.length,
67
67
  note: "No orders or sign-ins for 60+ days.",
68
68
  items: a.dormantTenants.map((t) => ({
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/_overview/attention-groups.ts"],"sourcesContent":["import type { PlatformCockpit } from '@withpotter/lib/admin-api';\nimport { logoOf } from '../commerce/merchant-meta';\nimport { inCurrency, overdueOutstanding } from './overdue-outstanding';\n\nexport interface AttentionItem {\n id: string;\n /** The merchant the row is about, for views that open it in place. */\n tenantId: string;\n href: string;\n title: string;\n detail: string;\n value: string;\n /** The merchant's logo, once the cockpit sends one; initials until then. */\n logo?: string | null;\n}\n\nexport interface AttentionGroup {\n key: string;\n title: string;\n count: number;\n note?: string;\n items: AttentionItem[];\n}\n\nconst tenant = (id: string) => `/admin/tenants/${id}`;\n\n/** The cockpit's exceptions as uniform groups, most urgent kinds first. */\nexport function attentionGroups(\n a: PlatformCockpit['attention'],\n): AttentionGroup[] {\n return [\n {\n key: 'settlements',\n title: 'Failed payouts',\n count: a.failedSettlements.count,\n items: a.failedSettlements.sample.map((s) => ({\n id: s.id,\n logo: logoOf(s),\n tenantId: s.tenantId,\n href: tenant(s.tenantId),\n title: s.tenantName,\n detail: 'Payout failed',\n value: inCurrency(s.netAmount, s.currency),\n })),\n },\n {\n key: 'invoices',\n title: 'Overdue bills',\n count: a.overdueInvoices.count,\n note: overdueOutstanding(a.overdueInvoices) ?? undefined,\n items: a.overdueInvoices.sample.map((i) => ({\n id: i.id,\n logo: logoOf(i),\n tenantId: i.tenantId,\n href: tenant(i.tenantId),\n title: i.tenantName,\n detail: `Bill ${i.invoiceNumber}`,\n value: inCurrency(i.amountDue, i.currency),\n })),\n },\n {\n key: 'review',\n title: 'Merchants to review',\n count: a.needsReview.length,\n items: a.needsReview.map((t) => ({\n id: t.id,\n logo: logoOf(t),\n tenantId: t.id,\n href: tenant(t.id),\n title: t.businessName,\n detail: t.slug,\n value: t.status.replace(/_/g, ' '),\n })),\n },\n {\n key: 'trials',\n title: 'Free trials ending',\n count: a.trialsEndingSoon.length,\n items: a.trialsEndingSoon.map((t) => ({\n id: t.id,\n logo: logoOf(t),\n tenantId: t.id,\n href: tenant(t.id),\n title: t.businessName,\n detail: t.slug,\n value: t.daysLeft === 0 ? 'Today' : `${t.daysLeft} ${t.daysLeft === 1 ? 'day' : 'days'} left`,\n })),\n },\n {\n key: 'dormant',\n title: 'Inactive',\n count: a.dormantTenants.length,\n note: 'No orders or sign-ins for 60+ days.',\n items: a.dormantTenants.map((t) => ({\n id: t.id,\n logo: logoOf(t),\n tenantId: t.id,\n href: tenant(t.id),\n title: t.businessName,\n detail: t.slug,\n value: '60d+',\n })),\n },\n ];\n}\n"],"mappings":"AACA,SAAS,cAAc;AACvB,SAAS,YAAY,0BAA0B;AAsB/C,MAAM,SAAS,CAAC,OAAe,kBAAkB,EAAE;AAG5C,SAAS,gBACd,GACkB;AAClB,SAAO;AAAA,IACL;AAAA,MACE,KAAK;AAAA,MACL,OAAO;AAAA,MACP,OAAO,EAAE,kBAAkB;AAAA,MAC3B,OAAO,EAAE,kBAAkB,OAAO,IAAI,CAAC,OAAO;AAAA,QAC5C,IAAI,EAAE;AAAA,QACN,MAAM,OAAO,CAAC;AAAA,QACd,UAAU,EAAE;AAAA,QACZ,MAAM,OAAO,EAAE,QAAQ;AAAA,QACvB,OAAO,EAAE;AAAA,QACT,QAAQ;AAAA,QACR,OAAO,WAAW,EAAE,WAAW,EAAE,QAAQ;AAAA,MAC3C,EAAE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,KAAK;AAAA,MACL,OAAO;AAAA,MACP,OAAO,EAAE,gBAAgB;AAAA,MACzB,MAAM,mBAAmB,EAAE,eAAe,KAAK;AAAA,MAC/C,OAAO,EAAE,gBAAgB,OAAO,IAAI,CAAC,OAAO;AAAA,QAC1C,IAAI,EAAE;AAAA,QACN,MAAM,OAAO,CAAC;AAAA,QACd,UAAU,EAAE;AAAA,QACZ,MAAM,OAAO,EAAE,QAAQ;AAAA,QACvB,OAAO,EAAE;AAAA,QACT,QAAQ,QAAQ,EAAE,aAAa;AAAA,QAC/B,OAAO,WAAW,EAAE,WAAW,EAAE,QAAQ;AAAA,MAC3C,EAAE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,KAAK;AAAA,MACL,OAAO;AAAA,MACP,OAAO,EAAE,YAAY;AAAA,MACrB,OAAO,EAAE,YAAY,IAAI,CAAC,OAAO;AAAA,QAC/B,IAAI,EAAE;AAAA,QACN,MAAM,OAAO,CAAC;AAAA,QACd,UAAU,EAAE;AAAA,QACZ,MAAM,OAAO,EAAE,EAAE;AAAA,QACjB,OAAO,EAAE;AAAA,QACT,QAAQ,EAAE;AAAA,QACV,OAAO,EAAE,OAAO,QAAQ,MAAM,GAAG;AAAA,MACnC,EAAE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,KAAK;AAAA,MACL,OAAO;AAAA,MACP,OAAO,EAAE,iBAAiB;AAAA,MAC1B,OAAO,EAAE,iBAAiB,IAAI,CAAC,OAAO;AAAA,QACpC,IAAI,EAAE;AAAA,QACN,MAAM,OAAO,CAAC;AAAA,QACd,UAAU,EAAE;AAAA,QACZ,MAAM,OAAO,EAAE,EAAE;AAAA,QACjB,OAAO,EAAE;AAAA,QACT,QAAQ,EAAE;AAAA,QACV,OAAO,EAAE,aAAa,IAAI,UAAU,GAAG,EAAE,QAAQ,IAAI,EAAE,aAAa,IAAI,QAAQ,MAAM;AAAA,MACxF,EAAE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,KAAK;AAAA,MACL,OAAO;AAAA,MACP,OAAO,EAAE,eAAe;AAAA,MACxB,MAAM;AAAA,MACN,OAAO,EAAE,eAAe,IAAI,CAAC,OAAO;AAAA,QAClC,IAAI,EAAE;AAAA,QACN,MAAM,OAAO,CAAC;AAAA,QACd,UAAU,EAAE;AAAA,QACZ,MAAM,OAAO,EAAE,EAAE;AAAA,QACjB,OAAO,EAAE;AAAA,QACT,QAAQ,EAAE;AAAA,QACV,OAAO;AAAA,MACT,EAAE;AAAA,IACJ;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/_overview/attention-groups.ts"],"sourcesContent":["import type { PlatformCockpit } from '@withpotter/lib/admin-api';\nimport { logoOf } from '../commerce/merchant-meta';\nimport { inCurrency, overdueOutstanding } from './overdue-outstanding';\n\nexport interface AttentionItem {\n id: string;\n /** The merchant the row is about, for views that open it in place. */\n tenantId: string;\n href: string;\n title: string;\n detail: string;\n value: string;\n /** The merchant's logo, once the cockpit sends one; initials until then. */\n logo?: string | null;\n}\n\nexport interface AttentionGroup {\n key: string;\n title: string;\n count: number;\n note?: string;\n items: AttentionItem[];\n}\n\nconst tenant = (id: string) => `/admin/tenants/${id}`;\n\n/** The cockpit's exceptions as uniform groups, most urgent kinds first. */\nexport function attentionGroups(\n a: PlatformCockpit['attention'],\n): AttentionGroup[] {\n return [\n {\n key: 'settlements',\n title: 'Failed payouts',\n count: a.failedSettlements.count,\n items: a.failedSettlements.sample.map((s) => ({\n id: s.id,\n logo: logoOf(s),\n tenantId: s.tenantId,\n href: tenant(s.tenantId),\n title: s.tenantName,\n detail: 'Settlement failed',\n value: inCurrency(s.netAmount, s.currency),\n })),\n },\n {\n key: 'invoices',\n title: 'Overdue',\n count: a.overdueInvoices.count,\n note: overdueOutstanding(a.overdueInvoices) ?? undefined,\n items: a.overdueInvoices.sample.map((i) => ({\n id: i.id,\n logo: logoOf(i),\n tenantId: i.tenantId,\n href: tenant(i.tenantId),\n title: i.tenantName,\n detail: `Invoice ${i.invoiceNumber}`,\n value: inCurrency(i.amountDue, i.currency),\n })),\n },\n {\n key: 'review',\n title: 'Review',\n count: a.needsReview.length,\n items: a.needsReview.map((t) => ({\n id: t.id,\n logo: logoOf(t),\n tenantId: t.id,\n href: tenant(t.id),\n title: t.businessName,\n detail: t.slug,\n value: t.status,\n })),\n },\n {\n key: 'trials',\n title: 'Trials ending',\n count: a.trialsEndingSoon.length,\n items: a.trialsEndingSoon.map((t) => ({\n id: t.id,\n logo: logoOf(t),\n tenantId: t.id,\n href: tenant(t.id),\n title: t.businessName,\n detail: t.slug,\n value: t.daysLeft === 0 ? 'Today' : `${t.daysLeft}d left`,\n })),\n },\n {\n key: 'dormant',\n title: 'Dormant',\n count: a.dormantTenants.length,\n note: 'No orders or sign-ins for 60+ days.',\n items: a.dormantTenants.map((t) => ({\n id: t.id,\n logo: logoOf(t),\n tenantId: t.id,\n href: tenant(t.id),\n title: t.businessName,\n detail: t.slug,\n value: '60d+',\n })),\n },\n ];\n}\n"],"mappings":"AACA,SAAS,cAAc;AACvB,SAAS,YAAY,0BAA0B;AAsB/C,MAAM,SAAS,CAAC,OAAe,kBAAkB,EAAE;AAG5C,SAAS,gBACd,GACkB;AAClB,SAAO;AAAA,IACL;AAAA,MACE,KAAK;AAAA,MACL,OAAO;AAAA,MACP,OAAO,EAAE,kBAAkB;AAAA,MAC3B,OAAO,EAAE,kBAAkB,OAAO,IAAI,CAAC,OAAO;AAAA,QAC5C,IAAI,EAAE;AAAA,QACN,MAAM,OAAO,CAAC;AAAA,QACd,UAAU,EAAE;AAAA,QACZ,MAAM,OAAO,EAAE,QAAQ;AAAA,QACvB,OAAO,EAAE;AAAA,QACT,QAAQ;AAAA,QACR,OAAO,WAAW,EAAE,WAAW,EAAE,QAAQ;AAAA,MAC3C,EAAE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,KAAK;AAAA,MACL,OAAO;AAAA,MACP,OAAO,EAAE,gBAAgB;AAAA,MACzB,MAAM,mBAAmB,EAAE,eAAe,KAAK;AAAA,MAC/C,OAAO,EAAE,gBAAgB,OAAO,IAAI,CAAC,OAAO;AAAA,QAC1C,IAAI,EAAE;AAAA,QACN,MAAM,OAAO,CAAC;AAAA,QACd,UAAU,EAAE;AAAA,QACZ,MAAM,OAAO,EAAE,QAAQ;AAAA,QACvB,OAAO,EAAE;AAAA,QACT,QAAQ,WAAW,EAAE,aAAa;AAAA,QAClC,OAAO,WAAW,EAAE,WAAW,EAAE,QAAQ;AAAA,MAC3C,EAAE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,KAAK;AAAA,MACL,OAAO;AAAA,MACP,OAAO,EAAE,YAAY;AAAA,MACrB,OAAO,EAAE,YAAY,IAAI,CAAC,OAAO;AAAA,QAC/B,IAAI,EAAE;AAAA,QACN,MAAM,OAAO,CAAC;AAAA,QACd,UAAU,EAAE;AAAA,QACZ,MAAM,OAAO,EAAE,EAAE;AAAA,QACjB,OAAO,EAAE;AAAA,QACT,QAAQ,EAAE;AAAA,QACV,OAAO,EAAE;AAAA,MACX,EAAE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,KAAK;AAAA,MACL,OAAO;AAAA,MACP,OAAO,EAAE,iBAAiB;AAAA,MAC1B,OAAO,EAAE,iBAAiB,IAAI,CAAC,OAAO;AAAA,QACpC,IAAI,EAAE;AAAA,QACN,MAAM,OAAO,CAAC;AAAA,QACd,UAAU,EAAE;AAAA,QACZ,MAAM,OAAO,EAAE,EAAE;AAAA,QACjB,OAAO,EAAE;AAAA,QACT,QAAQ,EAAE;AAAA,QACV,OAAO,EAAE,aAAa,IAAI,UAAU,GAAG,EAAE,QAAQ;AAAA,MACnD,EAAE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,KAAK;AAAA,MACL,OAAO;AAAA,MACP,OAAO,EAAE,eAAe;AAAA,MACxB,MAAM;AAAA,MACN,OAAO,EAAE,eAAe,IAAI,CAAC,OAAO;AAAA,QAClC,IAAI,EAAE;AAAA,QACN,MAAM,OAAO,CAAC;AAAA,QACd,UAAU,EAAE;AAAA,QACZ,MAAM,OAAO,EAAE,EAAE;AAAA,QACjB,OAAO,EAAE;AAAA,QACT,QAAQ,EAAE;AAAA,QACV,OAAO;AAAA,MACT,EAAE;AAAA,IACJ;AAAA,EACF;AACF;","names":[]}
@@ -14,43 +14,39 @@ function AttentionPanel({
14
14
  const total = groups.reduce((sum, g) => sum + g.count, 0);
15
15
  const [picked, setPicked] = useState(null);
16
16
  const active = groups.find((g) => g.key === picked) ?? [...groups].sort((a, b) => b.count - a.count)[0];
17
- const unknown = attention.unavailable ?? [];
18
- return /* @__PURE__ */ jsxs(Panel, { title: "Needs attention", count: total, flush: true, children: [
19
- unknown.length > 0 && /* @__PURE__ */ jsx("p", { className: styles.note, children: "Some items couldn't be loaded, so this list may be missing things." }),
20
- !active ? /* @__PURE__ */ jsx("div", { className: styles.clear, children: /* @__PURE__ */ jsx(EmptyNote, { children: unknown.length > 0 ? "Nothing to show right now." : "Nothing needs attention in this period." }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
21
- /* @__PURE__ */ jsx(
22
- "div",
23
- {
24
- className: styles.filters,
25
- role: "group",
26
- "aria-label": "Show one kind of item",
27
- children: groups.map((g) => /* @__PURE__ */ jsxs(
28
- "button",
29
- {
30
- type: "button",
31
- "aria-pressed": g.key === active.key,
32
- className: styles.filter,
33
- onClick: () => setPicked(g.key),
34
- children: [
35
- g.title,
36
- /* @__PURE__ */ jsx("span", { children: g.count })
37
- ]
38
- },
39
- g.key
40
- ))
41
- }
42
- ),
43
- active.note && /* @__PURE__ */ jsx("p", { className: styles.note, children: active.note }),
44
- /* @__PURE__ */ jsx("ul", { className: styles.rows, children: active.items.map((item) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(Link, { href: item.href, className: styles.row, children: [
45
- item.logo && /* @__PURE__ */ jsx(EntityAvatar, { name: item.title, src: item.logo, size: 32 }),
46
- /* @__PURE__ */ jsxs("span", { className: styles.rowMain, children: [
47
- /* @__PURE__ */ jsx("strong", { children: item.title }),
48
- /* @__PURE__ */ jsx("small", { children: item.detail })
49
- ] }),
50
- /* @__PURE__ */ jsx("span", { className: styles.rowValue, children: item.value })
51
- ] }) }, item.id)) })
52
- ] })
53
- ] });
17
+ return /* @__PURE__ */ jsx(Panel, { title: "Needs attention", count: total, flush: true, children: !active ? /* @__PURE__ */ jsx("div", { className: styles.clear, children: /* @__PURE__ */ jsx(EmptyNote, { children: "No exceptions in this period." }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
18
+ /* @__PURE__ */ jsx(
19
+ "div",
20
+ {
21
+ className: styles.filters,
22
+ role: "group",
23
+ "aria-label": "Show exceptions of one kind",
24
+ children: groups.map((g) => /* @__PURE__ */ jsxs(
25
+ "button",
26
+ {
27
+ type: "button",
28
+ "aria-pressed": g.key === active.key,
29
+ className: styles.filter,
30
+ onClick: () => setPicked(g.key),
31
+ children: [
32
+ g.title,
33
+ /* @__PURE__ */ jsx("span", { children: g.count })
34
+ ]
35
+ },
36
+ g.key
37
+ ))
38
+ }
39
+ ),
40
+ active.note && /* @__PURE__ */ jsx("p", { className: styles.note, children: active.note }),
41
+ /* @__PURE__ */ jsx("ul", { className: styles.rows, children: active.items.map((item) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(Link, { href: item.href, className: styles.row, children: [
42
+ item.logo && /* @__PURE__ */ jsx(EntityAvatar, { name: item.title, src: item.logo, size: 32 }),
43
+ /* @__PURE__ */ jsxs("span", { className: styles.rowMain, children: [
44
+ /* @__PURE__ */ jsx("strong", { children: item.title }),
45
+ /* @__PURE__ */ jsx("small", { children: item.detail })
46
+ ] }),
47
+ /* @__PURE__ */ jsx("span", { className: styles.rowValue, children: item.value })
48
+ ] }) }, item.id)) })
49
+ ] }) });
54
50
  }
55
51
  export {
56
52
  AttentionPanel
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/_overview/attention-panel.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport Link from 'next/link';\nimport { EmptyNote } from '@withpotter/ui-kit';\nimport type { PlatformCockpit } from '@withpotter/lib/admin-api';\nimport { EntityAvatar } from '../_fx';\nimport { Panel } from '../_ui';\nimport { attentionGroups } from './attention-groups';\nimport styles from './attention.module.css';\n\n/**\n * Every exception in one card: a filter per kind, the rows scrolling inside.\n * The busiest kind is open first; empty kinds are not offered at all.\n */\nexport function AttentionPanel({\n attention,\n}: {\n attention: PlatformCockpit['attention'];\n}) {\n const groups = attentionGroups(attention).filter((g) => g.count);\n const total = groups.reduce((sum, g) => sum + g.count, 0);\n const [picked, setPicked] = useState<string | null>(null);\n const active =\n groups.find((g) => g.key === picked) ??\n [...groups].sort((a, b) => b.count - a.count)[0];\n\n const unknown = attention.unavailable ?? [];\n\n return (\n <Panel title=\"Needs attention\" count={total} flush>\n {unknown.length > 0 && (\n <p className={styles.note}>\n Some items couldn&apos;t be loaded, so this list may be missing things.\n </p>\n )}\n {!active ? (\n <div className={styles.clear}>\n <EmptyNote>\n {unknown.length > 0\n ? 'Nothing to show right now.'\n : 'Nothing needs attention in this period.'}\n </EmptyNote>\n </div>\n ) : (\n <>\n <div\n className={styles.filters}\n role=\"group\"\n aria-label=\"Show one kind of item\"\n >\n {groups.map((g) => (\n <button\n key={g.key}\n type=\"button\"\n aria-pressed={g.key === active.key}\n className={styles.filter}\n onClick={() => setPicked(g.key)}\n >\n {g.title}\n <span>{g.count}</span>\n </button>\n ))}\n </div>\n {active.note && <p className={styles.note}>{active.note}</p>}\n <ul className={styles.rows}>\n {active.items.map((item) => (\n <li key={item.id}>\n <Link href={item.href} className={styles.row}>\n {item.logo && (\n <EntityAvatar name={item.title} src={item.logo} size={32} />\n )}\n <span className={styles.rowMain}>\n <strong>{item.title}</strong>\n <small>{item.detail}</small>\n </span>\n <span className={styles.rowValue}>{item.value}</span>\n </Link>\n </li>\n ))}\n </ul>\n </>\n )}\n </Panel>\n );\n}\n"],"mappings":";AAgCQ,SAaA,UAbA,KAoBM,YApBN;AA9BR,SAAS,gBAAgB;AACzB,OAAO,UAAU;AACjB,SAAS,iBAAiB;AAE1B,SAAS,oBAAoB;AAC7B,SAAS,aAAa;AACtB,SAAS,uBAAuB;AAChC,OAAO,YAAY;AAMZ,SAAS,eAAe;AAAA,EAC7B;AACF,GAEG;AACD,QAAM,SAAS,gBAAgB,SAAS,EAAE,OAAO,CAAC,MAAM,EAAE,KAAK;AAC/D,QAAM,QAAQ,OAAO,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,OAAO,CAAC;AACxD,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAwB,IAAI;AACxD,QAAM,SACJ,OAAO,KAAK,CAAC,MAAM,EAAE,QAAQ,MAAM,KACnC,CAAC,GAAG,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;AAEjD,QAAM,UAAU,UAAU,eAAe,CAAC;AAE1C,SACE,qBAAC,SAAM,OAAM,mBAAkB,OAAO,OAAO,OAAK,MAC/C;AAAA,YAAQ,SAAS,KAChB,oBAAC,OAAE,WAAW,OAAO,MAAM,gFAE3B;AAAA,IAED,CAAC,SACA,oBAAC,SAAI,WAAW,OAAO,OACrB,8BAAC,aACE,kBAAQ,SAAS,IACd,+BACA,2CACN,GACF,IAEA,iCACE;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,WAAW,OAAO;AAAA,UAClB,MAAK;AAAA,UACL,cAAW;AAAA,UAEV,iBAAO,IAAI,CAAC,MACX;AAAA,YAAC;AAAA;AAAA,cAEC,MAAK;AAAA,cACL,gBAAc,EAAE,QAAQ,OAAO;AAAA,cAC/B,WAAW,OAAO;AAAA,cAClB,SAAS,MAAM,UAAU,EAAE,GAAG;AAAA,cAE7B;AAAA,kBAAE;AAAA,gBACH,oBAAC,UAAM,YAAE,OAAM;AAAA;AAAA;AAAA,YAPV,EAAE;AAAA,UAQT,CACD;AAAA;AAAA,MACH;AAAA,MACC,OAAO,QAAQ,oBAAC,OAAE,WAAW,OAAO,MAAO,iBAAO,MAAK;AAAA,MACxD,oBAAC,QAAG,WAAW,OAAO,MACnB,iBAAO,MAAM,IAAI,CAAC,SACjB,oBAAC,QACC,+BAAC,QAAK,MAAM,KAAK,MAAM,WAAW,OAAO,KACtC;AAAA,aAAK,QACJ,oBAAC,gBAAa,MAAM,KAAK,OAAO,KAAK,KAAK,MAAM,MAAM,IAAI;AAAA,QAE5D,qBAAC,UAAK,WAAW,OAAO,SACtB;AAAA,8BAAC,YAAQ,eAAK,OAAM;AAAA,UACpB,oBAAC,WAAO,eAAK,QAAO;AAAA,WACtB;AAAA,QACA,oBAAC,UAAK,WAAW,OAAO,UAAW,eAAK,OAAM;AAAA,SAChD,KAVO,KAAK,EAWd,CACD,GACH;AAAA,OACF;AAAA,KAEJ;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/_overview/attention-panel.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport Link from 'next/link';\nimport { EmptyNote } from '@withpotter/ui-kit';\nimport type { PlatformCockpit } from '@withpotter/lib/admin-api';\nimport { EntityAvatar } from '../_fx';\nimport { Panel } from '../_ui';\nimport { attentionGroups } from './attention-groups';\nimport styles from './attention.module.css';\n\n/**\n * Every exception in one card: a filter per kind, the rows scrolling inside.\n * The busiest kind is open first; empty kinds are not offered at all.\n */\nexport function AttentionPanel({\n attention,\n}: {\n attention: PlatformCockpit['attention'];\n}) {\n const groups = attentionGroups(attention).filter((g) => g.count);\n const total = groups.reduce((sum, g) => sum + g.count, 0);\n const [picked, setPicked] = useState<string | null>(null);\n const active =\n groups.find((g) => g.key === picked) ??\n [...groups].sort((a, b) => b.count - a.count)[0];\n\n return (\n <Panel title=\"Needs attention\" count={total} flush>\n {!active ? (\n <div className={styles.clear}>\n <EmptyNote>No exceptions in this period.</EmptyNote>\n </div>\n ) : (\n <>\n <div\n className={styles.filters}\n role=\"group\"\n aria-label=\"Show exceptions of one kind\"\n >\n {groups.map((g) => (\n <button\n key={g.key}\n type=\"button\"\n aria-pressed={g.key === active.key}\n className={styles.filter}\n onClick={() => setPicked(g.key)}\n >\n {g.title}\n <span>{g.count}</span>\n </button>\n ))}\n </div>\n {active.note && <p className={styles.note}>{active.note}</p>}\n <ul className={styles.rows}>\n {active.items.map((item) => (\n <li key={item.id}>\n <Link href={item.href} className={styles.row}>\n {item.logo && (\n <EntityAvatar name={item.title} src={item.logo} size={32} />\n )}\n <span className={styles.rowMain}>\n <strong>{item.title}</strong>\n <small>{item.detail}</small>\n </span>\n <span className={styles.rowValue}>{item.value}</span>\n </Link>\n </li>\n ))}\n </ul>\n </>\n )}\n </Panel>\n );\n}\n"],"mappings":";AA+BU,SAGF,UAHE,KAUI,YAVJ;AA7BV,SAAS,gBAAgB;AACzB,OAAO,UAAU;AACjB,SAAS,iBAAiB;AAE1B,SAAS,oBAAoB;AAC7B,SAAS,aAAa;AACtB,SAAS,uBAAuB;AAChC,OAAO,YAAY;AAMZ,SAAS,eAAe;AAAA,EAC7B;AACF,GAEG;AACD,QAAM,SAAS,gBAAgB,SAAS,EAAE,OAAO,CAAC,MAAM,EAAE,KAAK;AAC/D,QAAM,QAAQ,OAAO,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,OAAO,CAAC;AACxD,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAwB,IAAI;AACxD,QAAM,SACJ,OAAO,KAAK,CAAC,MAAM,EAAE,QAAQ,MAAM,KACnC,CAAC,GAAG,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;AAEjD,SACE,oBAAC,SAAM,OAAM,mBAAkB,OAAO,OAAO,OAAK,MAC/C,WAAC,SACA,oBAAC,SAAI,WAAW,OAAO,OACrB,8BAAC,aAAU,2CAA6B,GAC1C,IAEA,iCACE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,WAAW,OAAO;AAAA,QAClB,MAAK;AAAA,QACL,cAAW;AAAA,QAEV,iBAAO,IAAI,CAAC,MACX;AAAA,UAAC;AAAA;AAAA,YAEC,MAAK;AAAA,YACL,gBAAc,EAAE,QAAQ,OAAO;AAAA,YAC/B,WAAW,OAAO;AAAA,YAClB,SAAS,MAAM,UAAU,EAAE,GAAG;AAAA,YAE7B;AAAA,gBAAE;AAAA,cACH,oBAAC,UAAM,YAAE,OAAM;AAAA;AAAA;AAAA,UAPV,EAAE;AAAA,QAQT,CACD;AAAA;AAAA,IACH;AAAA,IACC,OAAO,QAAQ,oBAAC,OAAE,WAAW,OAAO,MAAO,iBAAO,MAAK;AAAA,IACxD,oBAAC,QAAG,WAAW,OAAO,MACnB,iBAAO,MAAM,IAAI,CAAC,SACjB,oBAAC,QACC,+BAAC,QAAK,MAAM,KAAK,MAAM,WAAW,OAAO,KACtC;AAAA,WAAK,QACJ,oBAAC,gBAAa,MAAM,KAAK,OAAO,KAAK,KAAK,MAAM,MAAM,IAAI;AAAA,MAE5D,qBAAC,UAAK,WAAW,OAAO,SACtB;AAAA,4BAAC,YAAQ,eAAK,OAAM;AAAA,QACpB,oBAAC,WAAO,eAAK,QAAO;AAAA,SACtB;AAAA,MACA,oBAAC,UAAK,WAAW,OAAO,UAAW,eAAK,OAAM;AAAA,OAChD,KAVO,KAAK,EAWd,CACD,GACH;AAAA,KACF,GAEJ;AAEJ;","names":[]}
@@ -1,6 +1,9 @@
1
1
  /* Attention panel: filter per kind, rows scroll inside the card. */
2
2
 
3
3
  .filters {
4
+ position: sticky;
5
+ top: 0;
6
+ z-index: 1;
4
7
  display: flex;
5
8
  gap: 4px;
6
9
  padding: 0 20px 12px;
@@ -1,8 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { CountUp } from "../_fx";
4
- import { StatCard, formatMoney } from "../_ui";
5
- import { usePlatformRevenue } from "../hooks";
4
+ import { StatCard } from "../_ui";
6
5
  import { useModuleOn } from "../_shell/modules-context";
7
6
  import styles from "./control-room.module.css";
8
7
  import { useWaitingCases } from "./use-waiting-cases";
@@ -13,73 +12,46 @@ function VerificationCard() {
13
12
  StatCard,
14
13
  {
15
14
  compact: true,
16
- label: "Identity checks to review",
15
+ label: "KYC / KYB decisions",
17
16
  value: waiting === void 0 ? "\u2013" : /* @__PURE__ */ jsx(CountUp, { value: waiting }),
18
- sub: "Merchants waiting for a decision",
17
+ sub: "Businesses waiting for review",
19
18
  href: "/admin/verification",
20
19
  alert: !!waiting
21
20
  }
22
21
  );
23
22
  }
24
- function SubscriptionsCard() {
25
- const revenue = usePlatformRevenue().data;
26
- if (!revenue?.recurring.tracked) return null;
27
- return /* @__PURE__ */ jsx(
28
- StatCard,
29
- {
30
- compact: true,
31
- label: "Plan income",
32
- value: formatMoney(revenue.recurring.mrr, revenue.currency),
33
- sub: "What merchants pay for their plans each month",
34
- href: "/admin/revenue"
35
- }
36
- );
37
- }
38
23
  function ControlRoom({
39
24
  attention,
40
- currency = "",
41
- totals
25
+ currency = ""
42
26
  }) {
43
27
  const failed = attention.failedSettlements.count;
44
28
  const overdue = attention.overdueInvoices.count;
45
- const failedUnknown = !!attention.unavailable?.includes("failedSettlements");
46
- const overdueUnknown = !!attention.unavailable?.includes("overdueInvoices");
47
29
  const withdrawals = useModuleOn("withdrawals");
48
30
  const verification = useModuleOn("verification");
49
- return /* @__PURE__ */ jsxs("section", { className: styles.room, "aria-label": "Things that need action", children: [
31
+ return /* @__PURE__ */ jsxs("section", { className: styles.room, "aria-label": "Control room", children: [
50
32
  /* @__PURE__ */ jsx(
51
33
  StatCard,
52
34
  {
53
35
  compact: true,
54
36
  label: "Failed payouts",
55
- value: failedUnknown ? "\u2013" : /* @__PURE__ */ jsx(CountUp, { value: failed }),
56
- sub: failedUnknown ? "Could not be loaded" : "Payouts that did not reach the merchant",
37
+ value: /* @__PURE__ */ jsx(CountUp, { value: failed }),
38
+ sub: "Settlements that did not land",
57
39
  href: withdrawals ? "/admin/withdrawals?tab=failed" : void 0,
58
- alert: failedUnknown || failed > 0
40
+ alert: failed > 0
59
41
  }
60
42
  ),
61
43
  /* @__PURE__ */ jsx(
62
44
  StatCard,
63
45
  {
64
46
  compact: true,
65
- label: "Overdue bills",
66
- value: overdueUnknown ? "\u2013" : /* @__PURE__ */ jsx(CountUp, { value: overdue }),
67
- sub: overdueUnknown ? "Could not be loaded" : overdueOutstanding(attention.overdueInvoices, currency) ?? "Bills past their due date",
47
+ label: "Overdue invoices",
48
+ value: /* @__PURE__ */ jsx(CountUp, { value: overdue }),
49
+ sub: overdueOutstanding(attention.overdueInvoices, currency) ?? "Invoices past their due date",
68
50
  href: "/admin/revenue",
69
- alert: overdueUnknown || overdue > 0
70
- }
71
- ),
72
- verification && /* @__PURE__ */ jsx(VerificationCard, {}),
73
- totals?.verifiedWallets !== void 0 && /* @__PURE__ */ jsx(
74
- StatCard,
75
- {
76
- compact: true,
77
- label: "Verified wallets",
78
- value: /* @__PURE__ */ jsx(CountUp, { value: totals.verifiedWallets }),
79
- sub: "Merchants who have passed their identity checks"
51
+ alert: overdue > 0
80
52
  }
81
53
  ),
82
- /* @__PURE__ */ jsx(SubscriptionsCard, {})
54
+ verification && /* @__PURE__ */ jsx(VerificationCard, {})
83
55
  ] });
84
56
  }
85
57
  export {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/_overview/control-room.tsx"],"sourcesContent":["'use client';\n\nimport type { PlatformCockpit } from '@withpotter/lib/admin-api';\nimport { CountUp } from '../_fx';\nimport { StatCard, formatMoney } from '../_ui';\nimport { usePlatformRevenue } from '../hooks';\nimport { useModuleOn } from '../_shell/modules-context';\nimport styles from './control-room.module.css';\nimport { useWaitingCases } from './use-waiting-cases';\nimport { overdueOutstanding } from './overdue-outstanding';\n\n/** KYC / KYB waiting count; mounted only where Verification is served. */\nfunction VerificationCard() {\n const waiting = useWaitingCases().data;\n\n return (\n <StatCard\n compact\n label=\"Identity checks to review\"\n value={waiting === undefined ? '–' : <CountUp value={waiting} />}\n sub=\"Merchants waiting for a decision\"\n href=\"/admin/verification\"\n alert={!!waiting}\n />\n );\n}\n\n/** Monthly recurring revenue; shown only where billing feeds the figure, never as a made-up zero. */\nfunction SubscriptionsCard() {\n const revenue = usePlatformRevenue().data;\n\n if (!revenue?.recurring.tracked) return null;\n\n return (\n <StatCard\n compact\n label=\"Plan income\"\n value={formatMoney(revenue.recurring.mrr, revenue.currency)}\n sub=\"What merchants pay for their plans each month\"\n href=\"/admin/revenue\"\n />\n );\n}\n\n/**\n * The control room: every queue that needs a decision, then the revenue and\n * wallet figures, as one row of identical metric cards that wraps. A card opens its screen when this Box\n * serves that screen; a queue whose module is off is not shown.\n */\nexport function ControlRoom({\n attention,\n currency = '',\n totals,\n}: {\n attention: PlatformCockpit['attention'];\n /** Lifetime figures; a card whose figure the api does not send is left out. */\n totals?: PlatformCockpit['totals'];\n /** The currency filter's choice; narrows what overdue invoices owe. */\n currency?: string;\n}) {\n const failed = attention.failedSettlements.count;\n const overdue = attention.overdueInvoices.count;\n // A feed that failed to load is unknown, not zero.\n const failedUnknown = !!attention.unavailable?.includes('failedSettlements');\n const overdueUnknown = !!attention.unavailable?.includes('overdueInvoices');\n const withdrawals = useModuleOn('withdrawals');\n const verification = useModuleOn('verification');\n\n return (\n <section className={styles.room} aria-label=\"Things that need action\">\n <StatCard\n compact\n label=\"Failed payouts\"\n value={failedUnknown ? '–' : <CountUp value={failed} />}\n sub={\n failedUnknown\n ? 'Could not be loaded'\n : 'Payouts that did not reach the merchant'\n }\n href={withdrawals ? '/admin/withdrawals?tab=failed' : undefined}\n alert={failedUnknown || failed > 0}\n />\n <StatCard\n compact\n label=\"Overdue bills\"\n value={overdueUnknown ? '–' : <CountUp value={overdue} />}\n sub={\n overdueUnknown\n ? 'Could not be loaded'\n : (overdueOutstanding(attention.overdueInvoices, currency) ??\n 'Bills past their due date')\n }\n href=\"/admin/revenue\"\n alert={overdueUnknown || overdue > 0}\n />\n {verification && <VerificationCard />}\n {totals?.verifiedWallets !== undefined && (\n <StatCard\n compact\n label=\"Verified wallets\"\n value={<CountUp value={totals.verifiedWallets} />}\n sub=\"Merchants who have passed their identity checks\"\n />\n )}\n <SubscriptionsCard />\n </section>\n );\n}\n"],"mappings":";AAmB2C,cAkDvC,YAlDuC;AAhB3C,SAAS,eAAe;AACxB,SAAS,UAAU,mBAAmB;AACtC,SAAS,0BAA0B;AACnC,SAAS,mBAAmB;AAC5B,OAAO,YAAY;AACnB,SAAS,uBAAuB;AAChC,SAAS,0BAA0B;AAGnC,SAAS,mBAAmB;AAC1B,QAAM,UAAU,gBAAgB,EAAE;AAElC,SACE;AAAA,IAAC;AAAA;AAAA,MACC,SAAO;AAAA,MACP,OAAM;AAAA,MACN,OAAO,YAAY,SAAY,WAAM,oBAAC,WAAQ,OAAO,SAAS;AAAA,MAC9D,KAAI;AAAA,MACJ,MAAK;AAAA,MACL,OAAO,CAAC,CAAC;AAAA;AAAA,EACX;AAEJ;AAGA,SAAS,oBAAoB;AAC3B,QAAM,UAAU,mBAAmB,EAAE;AAErC,MAAI,CAAC,SAAS,UAAU,QAAS,QAAO;AAExC,SACE;AAAA,IAAC;AAAA;AAAA,MACC,SAAO;AAAA,MACP,OAAM;AAAA,MACN,OAAO,YAAY,QAAQ,UAAU,KAAK,QAAQ,QAAQ;AAAA,MAC1D,KAAI;AAAA,MACJ,MAAK;AAAA;AAAA,EACP;AAEJ;AAOO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA,WAAW;AAAA,EACX;AACF,GAMG;AACD,QAAM,SAAS,UAAU,kBAAkB;AAC3C,QAAM,UAAU,UAAU,gBAAgB;AAE1C,QAAM,gBAAgB,CAAC,CAAC,UAAU,aAAa,SAAS,mBAAmB;AAC3E,QAAM,iBAAiB,CAAC,CAAC,UAAU,aAAa,SAAS,iBAAiB;AAC1E,QAAM,cAAc,YAAY,aAAa;AAC7C,QAAM,eAAe,YAAY,cAAc;AAE/C,SACE,qBAAC,aAAQ,WAAW,OAAO,MAAM,cAAW,2BAC1C;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,SAAO;AAAA,QACP,OAAM;AAAA,QACN,OAAO,gBAAgB,WAAM,oBAAC,WAAQ,OAAO,QAAQ;AAAA,QACrD,KACE,gBACI,wBACA;AAAA,QAEN,MAAM,cAAc,kCAAkC;AAAA,QACtD,OAAO,iBAAiB,SAAS;AAAA;AAAA,IACnC;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,SAAO;AAAA,QACP,OAAM;AAAA,QACN,OAAO,iBAAiB,WAAM,oBAAC,WAAQ,OAAO,SAAS;AAAA,QACvD,KACE,iBACI,wBACC,mBAAmB,UAAU,iBAAiB,QAAQ,KACvD;AAAA,QAEN,MAAK;AAAA,QACL,OAAO,kBAAkB,UAAU;AAAA;AAAA,IACrC;AAAA,IACC,gBAAgB,oBAAC,oBAAiB;AAAA,IAClC,QAAQ,oBAAoB,UAC3B;AAAA,MAAC;AAAA;AAAA,QACC,SAAO;AAAA,QACP,OAAM;AAAA,QACN,OAAO,oBAAC,WAAQ,OAAO,OAAO,iBAAiB;AAAA,QAC/C,KAAI;AAAA;AAAA,IACN;AAAA,IAEF,oBAAC,qBAAkB;AAAA,KACrB;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/_overview/control-room.tsx"],"sourcesContent":["'use client';\n\nimport type { PlatformCockpit } from '@withpotter/lib/admin-api';\nimport { CountUp } from '../_fx';\nimport { StatCard } from '../_ui';\nimport { useModuleOn } from '../_shell/modules-context';\nimport styles from './control-room.module.css';\nimport { useWaitingCases } from './use-waiting-cases';\nimport { overdueOutstanding } from './overdue-outstanding';\n\n/** KYC / KYB waiting count; mounted only where Verification is served. */\nfunction VerificationCard() {\n const waiting = useWaitingCases().data;\n\n return (\n <StatCard\n compact\n label=\"KYC / KYB decisions\"\n value={waiting === undefined ? '–' : <CountUp value={waiting} />}\n sub=\"Businesses waiting for review\"\n href=\"/admin/verification\"\n alert={!!waiting}\n />\n );\n}\n\n/**\n * The control room: every queue that needs a decision, as one row of\n * identical metric cards that wraps. A card opens its screen when this Box\n * serves that screen; a queue whose module is off is not shown.\n */\nexport function ControlRoom({\n attention,\n currency = '',\n}: {\n attention: PlatformCockpit['attention'];\n /** The currency filter's choice; narrows what overdue invoices owe. */\n currency?: string;\n}) {\n const failed = attention.failedSettlements.count;\n const overdue = attention.overdueInvoices.count;\n const withdrawals = useModuleOn('withdrawals');\n const verification = useModuleOn('verification');\n\n return (\n <section className={styles.room} aria-label=\"Control room\">\n <StatCard\n compact\n label=\"Failed payouts\"\n value={<CountUp value={failed} />}\n sub=\"Settlements that did not land\"\n href={withdrawals ? '/admin/withdrawals?tab=failed' : undefined}\n alert={failed > 0}\n />\n <StatCard\n compact\n label=\"Overdue invoices\"\n value={<CountUp value={overdue} />}\n sub={\n overdueOutstanding(attention.overdueInvoices, currency) ??\n 'Invoices past their due date'\n }\n href=\"/admin/revenue\"\n alert={overdue > 0}\n />\n {verification && <VerificationCard />}\n </section>\n );\n}\n"],"mappings":";AAkB2C,cA2BvC,YA3BuC;AAf3C,SAAS,eAAe;AACxB,SAAS,gBAAgB;AACzB,SAAS,mBAAmB;AAC5B,OAAO,YAAY;AACnB,SAAS,uBAAuB;AAChC,SAAS,0BAA0B;AAGnC,SAAS,mBAAmB;AAC1B,QAAM,UAAU,gBAAgB,EAAE;AAElC,SACE;AAAA,IAAC;AAAA;AAAA,MACC,SAAO;AAAA,MACP,OAAM;AAAA,MACN,OAAO,YAAY,SAAY,WAAM,oBAAC,WAAQ,OAAO,SAAS;AAAA,MAC9D,KAAI;AAAA,MACJ,MAAK;AAAA,MACL,OAAO,CAAC,CAAC;AAAA;AAAA,EACX;AAEJ;AAOO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA,WAAW;AACb,GAIG;AACD,QAAM,SAAS,UAAU,kBAAkB;AAC3C,QAAM,UAAU,UAAU,gBAAgB;AAC1C,QAAM,cAAc,YAAY,aAAa;AAC7C,QAAM,eAAe,YAAY,cAAc;AAE/C,SACE,qBAAC,aAAQ,WAAW,OAAO,MAAM,cAAW,gBAC1C;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,SAAO;AAAA,QACP,OAAM;AAAA,QACN,OAAO,oBAAC,WAAQ,OAAO,QAAQ;AAAA,QAC/B,KAAI;AAAA,QACJ,MAAM,cAAc,kCAAkC;AAAA,QACtD,OAAO,SAAS;AAAA;AAAA,IAClB;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,SAAO;AAAA,QACP,OAAM;AAAA,QACN,OAAO,oBAAC,WAAQ,OAAO,SAAS;AAAA,QAChC,KACE,mBAAmB,UAAU,iBAAiB,QAAQ,KACtD;AAAA,QAEF,MAAK;AAAA,QACL,OAAO,UAAU;AAAA;AAAA,IACnB;AAAA,IACC,gBAAgB,oBAAC,oBAAiB;AAAA,KACrC;AAEJ;","names":[]}
@@ -8,17 +8,17 @@ function KpiRow({
8
8
  compare,
9
9
  caption
10
10
  }) {
11
- const delta = (kpi) => compare ? { pct: kpi.pct, caption: "vs the period before" } : void 0;
11
+ const delta = (kpi) => compare ? { pct: kpi.pct, caption: "vs prior period" } : void 0;
12
12
  const gmvKpi = rowFor(data.kpis.gmvByCurrency, focus);
13
13
  const gmvSpark = rowFor(
14
14
  data.trend.performanceSeriesByCurrency,
15
15
  focus
16
16
  )?.series.map((p) => p.gmv);
17
- return /* @__PURE__ */ jsxs("section", { "aria-label": "Key numbers", className: "adm-grid", children: [
17
+ return /* @__PURE__ */ jsxs("section", { "aria-label": "Performance indicators", className: "adm-grid", children: [
18
18
  /* @__PURE__ */ jsx("div", { className: "adm-span-3", children: /* @__PURE__ */ jsx(
19
19
  StatCard,
20
20
  {
21
- label: "Total sales",
21
+ label: "Merchant GMV",
22
22
  value: /* @__PURE__ */ jsx(
23
23
  MoneyLines,
24
24
  {
@@ -32,7 +32,7 @@ function KpiRow({
32
32
  delta: gmvKpi ? delta(gmvKpi) : void 0,
33
33
  sub: compare && gmvKpi ? void 0 : caption,
34
34
  spark: gmvSpark,
35
- hint: "The total value of paid orders in this period, before refunds. Each currency is shown on its own line and never added together."
35
+ hint: "Gross value of paid orders in the selected period, one line per currency. Currencies are never added together."
36
36
  }
37
37
  ) }),
38
38
  /* @__PURE__ */ jsx("div", { className: "adm-span-3", children: /* @__PURE__ */ jsx(
@@ -44,13 +44,13 @@ function KpiRow({
44
44
  delta: delta(data.kpis.orders),
45
45
  sub: compare ? void 0 : "Paid orders, all time",
46
46
  spark: data.sparklines.orders,
47
- hint: "Paid orders across all merchants, in every currency."
47
+ hint: "Paid orders across all merchant workspaces, in every currency."
48
48
  }
49
49
  ) }),
50
50
  /* @__PURE__ */ jsx("div", { className: "adm-span-3", children: /* @__PURE__ */ jsx(
51
51
  StatCard,
52
52
  {
53
- label: "New merchants",
53
+ label: "New workspaces",
54
54
  value: integer.format(data.kpis.newTenants.current),
55
55
  href: "/admin/tenants",
56
56
  delta: delta(data.kpis.newTenants),
@@ -61,7 +61,7 @@ function KpiRow({
61
61
  /* @__PURE__ */ jsx("div", { className: "adm-span-3", children: /* @__PURE__ */ jsx(
62
62
  StatCard,
63
63
  {
64
- label: "New user accounts",
64
+ label: "New accounts",
65
65
  value: integer.format(data.kpis.newAccounts.current),
66
66
  href: "/admin/accounts",
67
67
  delta: delta(data.kpis.newAccounts),