@withpotter/pages-admin 0.13.0-next.101 → 0.13.0-next.103

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 (60) hide show
  1. package/dist/admin/_pos/tenant-operators.js +73 -0
  2. package/dist/admin/_pos/tenant-operators.js.map +1 -0
  3. package/dist/admin/_pos/tenant-pos-figures.js +82 -0
  4. package/dist/admin/_pos/tenant-pos-figures.js.map +1 -0
  5. package/dist/admin/_pos/use-pos.js +12 -0
  6. package/dist/admin/_pos/use-pos.js.map +1 -1
  7. package/dist/admin/hooks.js +19 -0
  8. package/dist/admin/hooks.js.map +1 -1
  9. package/dist/admin/pos/shifts-tab.js +15 -8
  10. package/dist/admin/pos/shifts-tab.js.map +1 -1
  11. package/dist/admin/revenue/cohort-panel.js +5 -5
  12. package/dist/admin/revenue/cohort-panel.js.map +1 -1
  13. package/dist/admin/revenue/health-panel.js +17 -17
  14. package/dist/admin/revenue/health-panel.js.map +1 -1
  15. package/dist/admin/revenue/ledger-model.js +7 -0
  16. package/dist/admin/revenue/ledger-model.js.map +1 -1
  17. package/dist/admin/revenue/ledger.module.css +43 -0
  18. package/dist/admin/revenue/merchant-headline.js +37 -0
  19. package/dist/admin/revenue/merchant-headline.js.map +1 -0
  20. package/dist/admin/revenue/merchant-payments-view.js +46 -22
  21. package/dist/admin/revenue/merchant-payments-view.js.map +1 -1
  22. package/dist/admin/revenue/merchants-view.js +99 -0
  23. package/dist/admin/revenue/merchants-view.js.map +1 -0
  24. package/dist/admin/revenue/payment-drawer.js +53 -0
  25. package/dist/admin/revenue/payment-drawer.js.map +1 -0
  26. package/dist/admin/revenue/plan-mix-panel.js +1 -1
  27. package/dist/admin/revenue/plan-mix-panel.js.map +1 -1
  28. package/dist/admin/revenue/plan-payment-drawer.js +42 -0
  29. package/dist/admin/revenue/plan-payment-drawer.js.map +1 -0
  30. package/dist/admin/revenue/print-summary-button.js +34 -0
  31. package/dist/admin/revenue/print-summary-button.js.map +1 -0
  32. package/dist/admin/revenue/quality-panel.js +19 -19
  33. package/dist/admin/revenue/quality-panel.js.map +1 -1
  34. package/dist/admin/revenue/revenue-kpis.js +64 -50
  35. package/dist/admin/revenue/revenue-kpis.js.map +1 -1
  36. package/dist/admin/revenue/revenue-overview.js +68 -50
  37. package/dist/admin/revenue/revenue-overview.js.map +1 -1
  38. package/dist/admin/revenue/revenue-retention.js +17 -13
  39. package/dist/admin/revenue/revenue-retention.js.map +1 -1
  40. package/dist/admin/revenue/revenue-workspace.js +16 -0
  41. package/dist/admin/revenue/revenue-workspace.js.map +1 -1
  42. package/dist/admin/revenue/subscriptions-panel.js +14 -14
  43. package/dist/admin/revenue/subscriptions-panel.js.map +1 -1
  44. package/dist/admin/revenue/subscriptions-view.js +20 -20
  45. package/dist/admin/revenue/subscriptions-view.js.map +1 -1
  46. package/dist/admin/revenue/summary-download.js +34 -0
  47. package/dist/admin/revenue/summary-download.js.map +1 -0
  48. package/dist/admin/revenue/summary-print.js +82 -0
  49. package/dist/admin/revenue/summary-print.js.map +1 -0
  50. package/dist/admin/revenue/summary-rows.js +44 -0
  51. package/dist/admin/revenue/summary-rows.js.map +1 -0
  52. package/dist/admin/revenue/top-tenants-panel.js +1 -1
  53. package/dist/admin/revenue/top-tenants-panel.js.map +1 -1
  54. package/dist/admin/revenue/url-filters.js +36 -0
  55. package/dist/admin/revenue/url-filters.js.map +1 -0
  56. package/dist/admin/revenue/use-url-filters.js +24 -0
  57. package/dist/admin/revenue/use-url-filters.js.map +1 -0
  58. package/dist/admin/tenants/[id]/tenant-pos.js +53 -16
  59. package/dist/admin/tenants/[id]/tenant-pos.js.map +1 -1
  60. package/package.json +4 -4
@@ -0,0 +1,73 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useMemo } from "react";
4
+ import { TableRow, Th } from "@withpotter/ui-kit";
5
+ import { ListCard } from "../_ui/list-card";
6
+ import list from "../_ui/list-card.module.css";
7
+ import { posErrorText } from "./pos-rules";
8
+ import { accessLine } from "./pos-words";
9
+ import { useTenantOperators } from "./use-pos";
10
+ import pos from "./pos.module.css";
11
+ const sentence = (s) => s ? s.charAt(0).toUpperCase() + s.slice(1).replace(/_/g, " ") : s;
12
+ function TenantOperators({
13
+ terminals,
14
+ period
15
+ }) {
16
+ const live = useMemo(
17
+ () => terminals.filter((t) => t.status !== "revoked"),
18
+ [terminals]
19
+ );
20
+ const results = useTenantOperators(
21
+ live.map((t) => t.id),
22
+ period
23
+ );
24
+ const isLoading = results.some((r) => r.isLoading);
25
+ const failed = results.find((r) => r.isError);
26
+ const people = useMemo(() => {
27
+ const byId = /* @__PURE__ */ new Map();
28
+ for (const r of results) {
29
+ const t = r.data;
30
+ if (!t) continue;
31
+ for (const o of t.operators) {
32
+ const row = byId.get(o.id) ?? { ...o, at: [] };
33
+ row.at.push(t.label);
34
+ byId.set(o.id, row);
35
+ }
36
+ }
37
+ return [...byId.values()].sort((a, b) => a.name.localeCompare(b.name));
38
+ }, [results.map((r) => r.dataUpdatedAt).join(",")]);
39
+ const open = live.some((t) => t.operatorAccess === "any_member");
40
+ return /* @__PURE__ */ jsx(
41
+ ListCard,
42
+ {
43
+ title: "Operators",
44
+ count: people.length,
45
+ columns: 3,
46
+ minWidth: 560,
47
+ isLoading,
48
+ isError: !!failed && people.length === 0,
49
+ onRetry: () => results.forEach((r) => void r.refetch()),
50
+ errorTitle: posErrorText(failed?.error, "Operators did not load."),
51
+ isEmpty: people.length === 0,
52
+ emptyTitle: live.length === 0 ? "This business has no terminals, so no operators." : "Nobody can sign in at its terminals yet.",
53
+ controls: live[0] ? /* @__PURE__ */ jsx("span", { className: "adm-caption", children: open ? accessLine({ operatorAccess: "any_member" }) : accessLine(live[0]) }) : void 0,
54
+ head: /* @__PURE__ */ jsxs("tr", { children: [
55
+ /* @__PURE__ */ jsx(Th, { children: "Person" }),
56
+ /* @__PURE__ */ jsx(Th, { children: "Role" }),
57
+ /* @__PURE__ */ jsx(Th, { children: "Terminals" })
58
+ ] }),
59
+ children: people.map((o) => /* @__PURE__ */ jsxs(TableRow, { children: [
60
+ /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsxs("span", { className: pos.cellStack, children: [
61
+ /* @__PURE__ */ jsx("span", { className: pos.clip, children: o.name }),
62
+ !o.active && /* @__PURE__ */ jsx("span", { className: pos.subtle, children: "Removed" })
63
+ ] }) }),
64
+ /* @__PURE__ */ jsx("td", { className: `${list.nowrap} ${list.muted}`, children: sentence(o.role) }),
65
+ /* @__PURE__ */ jsx("td", { className: list.muted, children: o.at.join(", ") })
66
+ ] }, o.id))
67
+ }
68
+ );
69
+ }
70
+ export {
71
+ TenantOperators
72
+ };
73
+ //# sourceMappingURL=tenant-operators.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/admin/_pos/tenant-operators.tsx"],"sourcesContent":["'use client';\n\nimport { useMemo } from 'react';\nimport { TableRow, Th } from '@withpotter/ui-kit';\nimport type { PosTerminal, PosWindow } from '@withpotter/lib/admin-pos-types';\nimport { ListCard } from '../_ui/list-card';\nimport list from '../_ui/list-card.module.css';\nimport { posErrorText } from './pos-rules';\nimport { accessLine } from './pos-words';\nimport { useTenantOperators } from './use-pos';\nimport pos from './pos.module.css';\n\nconst sentence = (s: string) =>\n s ? s.charAt(0).toUpperCase() + s.slice(1).replace(/_/g, ' ') : s;\n\n/**\n * Everyone who can sign in at the business's terminals, once each, with the\n * terminals they can use. Read from each terminal's own record.\n */\nexport function TenantOperators({\n terminals,\n period,\n}: {\n terminals: PosTerminal[];\n period: PosWindow;\n}) {\n const live = useMemo(\n () => terminals.filter((t) => t.status !== 'revoked'),\n [terminals],\n );\n const results = useTenantOperators(\n live.map((t) => t.id),\n period,\n );\n const isLoading = results.some((r) => r.isLoading);\n const failed = results.find((r) => r.isError);\n\n const people = useMemo(() => {\n const byId = new Map<\n string,\n { id: string; name: string; role: string; active: boolean; at: string[] }\n >();\n\n for (const r of results) {\n const t = r.data;\n\n if (!t) continue;\n\n for (const o of t.operators) {\n const row = byId.get(o.id) ?? { ...o, at: [] };\n\n row.at.push(t.label);\n byId.set(o.id, row);\n }\n }\n\n return [...byId.values()].sort((a, b) => a.name.localeCompare(b.name));\n // `results` is a new array each render; its data changes are what matter.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [results.map((r) => r.dataUpdatedAt).join(',')]);\n\n const open = live.some((t) => t.operatorAccess === 'any_member');\n\n return (\n <ListCard\n title=\"Operators\"\n count={people.length}\n columns={3}\n minWidth={560}\n isLoading={isLoading}\n isError={!!failed && people.length === 0}\n onRetry={() => results.forEach((r) => void r.refetch())}\n errorTitle={posErrorText(failed?.error, 'Operators did not load.')}\n isEmpty={people.length === 0}\n emptyTitle={\n live.length === 0\n ? 'This business has no terminals, so no operators.'\n : 'Nobody can sign in at its terminals yet.'\n }\n controls={\n live[0] ? (\n <span className=\"adm-caption\">\n {open\n ? accessLine({ operatorAccess: 'any_member' })\n : accessLine(live[0])}\n </span>\n ) : undefined\n }\n head={\n <tr>\n <Th>Person</Th>\n <Th>Role</Th>\n <Th>Terminals</Th>\n </tr>\n }\n >\n {people.map((o) => (\n <TableRow key={o.id}>\n <td>\n <span className={pos.cellStack}>\n <span className={pos.clip}>{o.name}</span>\n {!o.active && <span className={pos.subtle}>Removed</span>}\n </span>\n </td>\n <td className={`${list.nowrap} ${list.muted}`}>{sentence(o.role)}</td>\n <td className={list.muted}>{o.at.join(', ')}</td>\n </TableRow>\n ))}\n </ListCard>\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,82 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { StatCard } from "../_ui";
4
+ import { amountLines } from "./pos-money";
5
+ import { isLongOpen } from "./pos-rules";
6
+ import { inPeriod, onlineTone } from "./pos-words";
7
+ import styles from "./pos.module.css";
8
+ function TenantPosFigures({
9
+ data,
10
+ phrase,
11
+ currency = ""
12
+ }) {
13
+ const live = data.terminals.filter((t) => t.status !== "revoked");
14
+ const online = live.filter((t) => t.online).length;
15
+ const offlineOverDay = live.filter((t) => {
16
+ const tone = onlineTone(t);
17
+ return tone === "stale" || tone === "never";
18
+ }).length;
19
+ const suspended = live.filter((t) => t.status === "suspended").length;
20
+ const longOpen = data.openShifts.filter(isLongOpen).length;
21
+ const money = amountLines(data.takings.amounts, currency);
22
+ return /* @__PURE__ */ jsxs(
23
+ "section",
24
+ {
25
+ className: styles.figures,
26
+ "aria-label": "This business's POS at a glance",
27
+ children: [
28
+ /* @__PURE__ */ jsx(
29
+ StatCard,
30
+ {
31
+ compact: true,
32
+ label: "Terminals",
33
+ value: String(live.length),
34
+ sub: `${data.terminals.length - live.length} revoked`
35
+ }
36
+ ),
37
+ /* @__PURE__ */ jsx(
38
+ StatCard,
39
+ {
40
+ compact: true,
41
+ label: "Online now",
42
+ value: String(online),
43
+ sub: `Of ${live.length}`
44
+ }
45
+ ),
46
+ /* @__PURE__ */ jsx(
47
+ StatCard,
48
+ {
49
+ compact: true,
50
+ label: "Offline over a day",
51
+ value: String(offlineOverDay),
52
+ alert: offlineOverDay > 0
53
+ }
54
+ ),
55
+ /* @__PURE__ */ jsx(StatCard, { compact: true, label: "Suspended", value: String(suspended) }),
56
+ /* @__PURE__ */ jsx(
57
+ StatCard,
58
+ {
59
+ compact: true,
60
+ label: "Open shifts",
61
+ value: String(data.openShifts.length),
62
+ sub: longOpen ? `${longOpen} open over 12 h` : void 0,
63
+ alert: longOpen > 0
64
+ }
65
+ ),
66
+ /* @__PURE__ */ jsx(
67
+ StatCard,
68
+ {
69
+ compact: true,
70
+ label: "Card payments",
71
+ value: data.takings.count.toLocaleString("en-US"),
72
+ sub: money.length ? `${money.join(" \xB7 ")}, ${phrase}` : `None ${inPeriod(phrase)}`
73
+ }
74
+ )
75
+ ]
76
+ }
77
+ );
78
+ }
79
+ export {
80
+ TenantPosFigures
81
+ };
82
+ //# sourceMappingURL=tenant-pos-figures.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/admin/_pos/tenant-pos-figures.tsx"],"sourcesContent":["'use client';\n\nimport type { TenantPos } from '@withpotter/lib/admin-pos-types';\nimport { StatCard } from '../_ui';\nimport { amountLines } from './pos-money';\nimport { isLongOpen } from './pos-rules';\nimport { inPeriod, onlineTone } from './pos-words';\nimport styles from './pos.module.css';\n\n/**\n * One business's POS at a glance, worked out from its own terminals and\n * shifts, so it matches what the merchant's POS page counts.\n */\nexport function TenantPosFigures({\n data,\n phrase,\n currency = '',\n}: {\n data: TenantPos;\n phrase: string;\n currency?: string;\n}) {\n const live = data.terminals.filter((t) => t.status !== 'revoked');\n const online = live.filter((t) => t.online).length;\n const offlineOverDay = live.filter((t) => {\n const tone = onlineTone(t);\n\n return tone === 'stale' || tone === 'never';\n }).length;\n const suspended = live.filter((t) => t.status === 'suspended').length;\n const longOpen = data.openShifts.filter(isLongOpen).length;\n const money = amountLines(data.takings.amounts, currency);\n\n return (\n <section\n className={styles.figures}\n aria-label=\"This business's POS at a glance\"\n >\n <StatCard\n compact\n label=\"Terminals\"\n value={String(live.length)}\n sub={`${data.terminals.length - live.length} revoked`}\n />\n <StatCard\n compact\n label=\"Online now\"\n value={String(online)}\n sub={`Of ${live.length}`}\n />\n <StatCard\n compact\n label=\"Offline over a day\"\n value={String(offlineOverDay)}\n alert={offlineOverDay > 0}\n />\n <StatCard compact label=\"Suspended\" value={String(suspended)} />\n <StatCard\n compact\n label=\"Open shifts\"\n value={String(data.openShifts.length)}\n sub={longOpen ? `${longOpen} open over 12 h` : undefined}\n alert={longOpen > 0}\n />\n <StatCard\n compact\n label=\"Card payments\"\n value={data.takings.count.toLocaleString('en-US')}\n sub={\n money.length\n ? `${money.join(' · ')}, ${phrase}`\n : `None ${inPeriod(phrase)}`\n }\n />\n </section>\n );\n}\n"],"mappings":";AAkCI,SAIE,KAJF;AA/BJ,SAAS,gBAAgB;AACzB,SAAS,mBAAmB;AAC5B,SAAS,kBAAkB;AAC3B,SAAS,UAAU,kBAAkB;AACrC,OAAO,YAAY;AAMZ,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA,WAAW;AACb,GAIG;AACD,QAAM,OAAO,KAAK,UAAU,OAAO,CAAC,MAAM,EAAE,WAAW,SAAS;AAChE,QAAM,SAAS,KAAK,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE;AAC5C,QAAM,iBAAiB,KAAK,OAAO,CAAC,MAAM;AACxC,UAAM,OAAO,WAAW,CAAC;AAEzB,WAAO,SAAS,WAAW,SAAS;AAAA,EACtC,CAAC,EAAE;AACH,QAAM,YAAY,KAAK,OAAO,CAAC,MAAM,EAAE,WAAW,WAAW,EAAE;AAC/D,QAAM,WAAW,KAAK,WAAW,OAAO,UAAU,EAAE;AACpD,QAAM,QAAQ,YAAY,KAAK,QAAQ,SAAS,QAAQ;AAExD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,OAAO;AAAA,MAClB,cAAW;AAAA,MAEX;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,SAAO;AAAA,YACP,OAAM;AAAA,YACN,OAAO,OAAO,KAAK,MAAM;AAAA,YACzB,KAAK,GAAG,KAAK,UAAU,SAAS,KAAK,MAAM;AAAA;AAAA,QAC7C;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,SAAO;AAAA,YACP,OAAM;AAAA,YACN,OAAO,OAAO,MAAM;AAAA,YACpB,KAAK,MAAM,KAAK,MAAM;AAAA;AAAA,QACxB;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,SAAO;AAAA,YACP,OAAM;AAAA,YACN,OAAO,OAAO,cAAc;AAAA,YAC5B,OAAO,iBAAiB;AAAA;AAAA,QAC1B;AAAA,QACA,oBAAC,YAAS,SAAO,MAAC,OAAM,aAAY,OAAO,OAAO,SAAS,GAAG;AAAA,QAC9D;AAAA,UAAC;AAAA;AAAA,YACC,SAAO;AAAA,YACP,OAAM;AAAA,YACN,OAAO,OAAO,KAAK,WAAW,MAAM;AAAA,YACpC,KAAK,WAAW,GAAG,QAAQ,oBAAoB;AAAA,YAC/C,OAAO,WAAW;AAAA;AAAA,QACpB;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,SAAO;AAAA,YACP,OAAM;AAAA,YACN,OAAO,KAAK,QAAQ,MAAM,eAAe,OAAO;AAAA,YAChD,KACE,MAAM,SACF,GAAG,MAAM,KAAK,QAAK,CAAC,KAAK,MAAM,KAC/B,QAAQ,SAAS,MAAM,CAAC;AAAA;AAAA,QAEhC;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
@@ -2,6 +2,7 @@
2
2
  import {
3
3
  keepPreviousData,
4
4
  useMutation,
5
+ useQueries,
5
6
  useQuery,
6
7
  useQueryClient
7
8
  } from "@tanstack/react-query";
@@ -52,6 +53,16 @@ function usePosShifts(filters, enabled = true) {
52
53
  retry
53
54
  });
54
55
  }
56
+ function useTenantOperators(terminalIds, w, enabled = true) {
57
+ return useQueries({
58
+ queries: terminalIds.map((id) => ({
59
+ queryKey: key("terminal", id, w),
60
+ queryFn: () => adminPosApi.terminal(id, w),
61
+ enabled,
62
+ retry
63
+ }))
64
+ });
65
+ }
55
66
  function useTenantPos(tenantId, w, enabled = true) {
56
67
  return useQuery({
57
68
  queryKey: key("tenant", tenantId, w),
@@ -83,6 +94,7 @@ export {
83
94
  usePosSummary,
84
95
  usePosTerminal,
85
96
  usePosTerminals,
97
+ useTenantOperators,
86
98
  useTenantPos,
87
99
  useTerminalStatus
88
100
  };
@@ -1 +1 @@
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":[]}
1
+ {"version":3,"sources":["../../../src/admin/_pos/use-pos.ts"],"sourcesContent":["'use client';\n\nimport {\n keepPreviousData,\n useMutation,\n useQueries,\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\n/**\n * A business's operators, gathered from its terminals: the admin API lists\n * operators per terminal, not per business, so each live terminal is asked\n * once (the same query its own page uses, so the cache is shared).\n */\nexport function useTenantOperators(\n terminalIds: string[],\n w: PosWindow,\n enabled = true,\n) {\n return useQueries({\n queries: terminalIds.map((id) => ({\n queryKey: key('terminal', id, w),\n queryFn: () => adminPosApi.terminal(id, w),\n enabled,\n retry,\n })),\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,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;AAOO,SAAS,mBACd,aACA,GACA,UAAU,MACV;AACA,SAAO,WAAW;AAAA,IAChB,SAAS,YAAY,IAAI,CAAC,QAAQ;AAAA,MAChC,UAAU,IAAI,YAAY,IAAI,CAAC;AAAA,MAC/B,SAAS,MAAM,YAAY,SAAS,IAAI,CAAC;AAAA,MACzC;AAAA,MACA;AAAA,IACF,EAAE;AAAA,EACJ,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":[]}
@@ -54,6 +54,23 @@ function useSubscriptionPayments(filters) {
54
54
  placeholderData: keepPreviousData
55
55
  });
56
56
  }
57
+ function useMerchantRevenue(filters) {
58
+ return useQuery({
59
+ queryKey: ["platform-admin", "revenue", "merchants", filters],
60
+ queryFn: () => platformAdminApi.merchantRevenue(filters),
61
+ retry: false,
62
+ staleTime: 3e4,
63
+ placeholderData: keepPreviousData
64
+ });
65
+ }
66
+ function useMerchantOptions(range) {
67
+ return useQuery({
68
+ queryKey: ["platform-admin", "revenue", "merchant-options", range],
69
+ queryFn: () => platformAdminApi.merchantOptions(range),
70
+ retry: false,
71
+ staleTime: 6e4
72
+ });
73
+ }
57
74
  function usePlatformTenantsAnalytics(range = {}) {
58
75
  return useQuery({
59
76
  queryKey: ["platform-admin", "tenants-analytics", range],
@@ -227,7 +244,9 @@ export {
227
244
  useAdminRoles,
228
245
  useAdminSession,
229
246
  useGatewayChannels,
247
+ useMerchantOptions,
230
248
  useMerchantPayments,
249
+ useMerchantRevenue,
231
250
  usePlatformAccounts,
232
251
  usePlatformAccountsAnalytics,
233
252
  usePlatformAdmins,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/admin/hooks.ts"],"sourcesContent":["'use client';\n\nimport { keepPreviousData, useQuery } from '@tanstack/react-query';\nimport { platformAdminApi, type LedgerFilters } from '@withpotter/lib/admin-api';\nimport type { TenantListParams } from '@withpotter/lib/admin-tenants-query';\nimport type { AccountStage } from '@withpotter/lib/admin-account-types';\n\nexport function useAdminSession(enabled = true) {\n return useQuery({\n queryKey: ['platform-admin', 'session'],\n queryFn: () => platformAdminApi.me(),\n enabled,\n retry: false,\n staleTime: 60_000,\n });\n}\n\nexport function usePlatformOverview() {\n return useQuery({\n queryKey: ['platform-admin', 'overview'],\n queryFn: () => platformAdminApi.overview(),\n });\n}\n\nexport function usePlatformCockpit(\n range: { startDate?: string; endDate?: string } = {},\n) {\n return useQuery({\n queryKey: ['platform-admin', 'cockpit', range],\n queryFn: () => platformAdminApi.cockpit(range),\n });\n}\n\nexport function usePlatformRevenue(\n range: { startDate?: string; endDate?: string } = {},\n) {\n return useQuery({\n queryKey: ['platform-admin', 'revenue', range],\n queryFn: () => platformAdminApi.revenue(range),\n });\n}\n\n/**\n * How customers paid, per channel. Not every box can say: where the route is\n * absent the query fails once (no retry) and `data` stays undefined, which the\n * panels read as \"nothing to show\".\n */\nexport function useGatewayChannels(\n range: { startDate?: string; endDate?: string } = {},\n) {\n return useQuery({\n queryKey: ['platform-admin', 'gateway-channels', range],\n queryFn: () => platformAdminApi.gatewayChannels(range),\n retry: false,\n staleTime: 60_000,\n });\n}\n\n/**\n * Revenue's Merchant payments view. A box without the route answers 404; the\n * view reads that as \"not available here\", never as a failure to retry.\n */\nexport function useMerchantPayments(filters: LedgerFilters) {\n return useQuery({\n queryKey: ['platform-admin', 'revenue', 'merchant-payments', filters],\n queryFn: () => platformAdminApi.merchantPayments(filters),\n retry: false,\n staleTime: 30_000,\n placeholderData: keepPreviousData,\n });\n}\n\n/** Revenue's Subscriptions view: every plan payment merchants made to Olive. */\nexport function useSubscriptionPayments(filters: LedgerFilters) {\n return useQuery({\n queryKey: ['platform-admin', 'revenue', 'subscription-payments', filters],\n queryFn: () => platformAdminApi.subscriptionPayments(filters),\n retry: false,\n staleTime: 30_000,\n placeholderData: keepPreviousData,\n });\n}\n\nexport function usePlatformTenantsAnalytics(\n range: { startDate?: string; endDate?: string } = {},\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenants-analytics', range],\n queryFn: () => platformAdminApi.tenantsAnalytics(range),\n });\n}\n\nexport function usePlatformTenants(params: TenantListParams) {\n return useQuery({\n queryKey: ['platform-admin', 'tenants', params],\n queryFn: () => platformAdminApi.tenants(params),\n });\n}\n\nexport function usePlatformTenant(\n id: string,\n range: { startDate?: string; endDate?: string } = {},\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant', id, range],\n queryFn: () => platformAdminApi.tenant(id, range),\n enabled: !!id,\n });\n}\n\nexport function useTenantAnalytics(\n id: string,\n range: { startDate?: string; endDate?: string } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-analytics', id, range],\n queryFn: () => platformAdminApi.tenantAnalytics(id, range),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantFinance(\n id: string,\n range: { startDate?: string; endDate?: string } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-finance', id, range],\n queryFn: () => platformAdminApi.tenantFinance(id, range),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantTransactions(\n id: string,\n params: {\n status?: string;\n type?: string;\n search?: string;\n page?: number;\n limit?: number;\n } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-transactions', id, params],\n queryFn: () => platformAdminApi.tenantTransactions(id, params),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantCustomers(\n id: string,\n params: {\n search?: string;\n type?: string;\n status?: string;\n page?: number;\n limit?: number;\n } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-customers', id, params],\n queryFn: () => platformAdminApi.tenantCustomers(id, params),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantProducts(\n id: string,\n params: { type?: string; search?: string } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-products', id, params],\n queryFn: () => platformAdminApi.tenantProducts(id, params),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantProductInsights(\n id: string,\n range: { startDate?: string } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-product-insights', id, range],\n queryFn: () => platformAdminApi.tenantProductInsights(id, range),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantCustomerInsights(\n id: string,\n range: { startDate?: string } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-customer-insights', id, range],\n queryFn: () => platformAdminApi.tenantCustomerInsights(id, range),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantInvoices(\n id: string,\n params: {\n status?: string;\n search?: string;\n page?: number;\n limit?: number;\n } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-invoices', id, params],\n queryFn: () => platformAdminApi.tenantInvoices(id, params),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantLoyalty(id: string, enabled = true) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-loyalty', id],\n queryFn: () => platformAdminApi.tenantLoyalty(id),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantDiscounts(\n id: string,\n params: {\n status?: string;\n discountType?: string;\n search?: string;\n page?: number;\n limit?: number;\n } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-discounts', id, params],\n queryFn: () => platformAdminApi.tenantDiscounts(id, params),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantSettlements(\n id: string,\n params: {\n page?: number;\n limit?: number;\n startDate?: string;\n endDate?: string;\n } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-settlements', id, params],\n queryFn: () => platformAdminApi.tenantSettlements(id, params),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantSubscriptions(\n id: string,\n params: {\n status?: string;\n planId?: string;\n page?: number;\n limit?: number;\n } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-subscriptions', id, params],\n queryFn: () => platformAdminApi.tenantSubscriptions(id, params),\n enabled: !!id && enabled,\n });\n}\n\nexport function usePlatformAccounts(params: {\n page?: number;\n pageSize?: number;\n search?: string;\n joinedFrom?: string;\n joinedTo?: string;\n stage?: AccountStage;\n}) {\n return useQuery({\n queryKey: ['platform-admin', 'accounts', params],\n queryFn: () => platformAdminApi.accounts(params),\n });\n}\n\nexport function usePlatformAccountsAnalytics(\n range: { startDate?: string; endDate?: string } = {},\n) {\n return useQuery({\n queryKey: ['platform-admin', 'accounts-analytics', range],\n queryFn: () => platformAdminApi.accountsAnalytics(range),\n });\n}\n\nexport function usePlatformAdmins() {\n return useQuery({\n queryKey: ['platform-admin', 'admins'],\n queryFn: () => platformAdminApi.admins(),\n });\n}\n\nexport function usePlatformAdminsAnalytics(\n range: { startDate?: string; endDate?: string } = {},\n) {\n return useQuery({\n queryKey: ['platform-admin', 'admins-analytics', range],\n queryFn: () => platformAdminApi.adminsAnalytics(range),\n });\n}\n\nexport function useAdminPermissions() {\n return useQuery({\n queryKey: ['platform-admin', 'admin-permissions'],\n queryFn: () => platformAdminApi.adminPermissions(),\n staleTime: 5 * 60_000,\n });\n}\n\nexport function useAdminRoles() {\n return useQuery({\n queryKey: ['platform-admin', 'roles'],\n queryFn: () => platformAdminApi.roles(),\n });\n}\n\nexport function useAccountSessions(id: string | null) {\n return useQuery({\n queryKey: ['platform-admin', 'account-sessions', id],\n queryFn: () => platformAdminApi.accountSessions(id!),\n enabled: !!id,\n });\n}\n\nexport function useAccountTenants(id: string | null) {\n return useQuery({\n queryKey: ['platform-admin', 'account-tenants', id],\n queryFn: () => platformAdminApi.accountTenants(id!),\n enabled: !!id,\n });\n}\n\nexport function usePlatformAudit(\n params: {\n page?: number;\n pageSize?: number;\n search?: string;\n action?: string;\n adminEmail?: string;\n from?: string;\n to?: string;\n } = {},\n) {\n return useQuery({\n queryKey: ['platform-admin', 'audit', params],\n queryFn: () => platformAdminApi.audit(params),\n });\n}\n\nexport function usePlatformAuditAnalytics(\n range: { startDate?: string; endDate?: string } = {},\n) {\n return useQuery({\n queryKey: ['platform-admin', 'audit-analytics', range],\n queryFn: () => platformAdminApi.auditAnalytics(range),\n });\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/admin/hooks.ts"],"sourcesContent":["'use client';\n\nimport { keepPreviousData, useQuery } from '@tanstack/react-query';\nimport { platformAdminApi, type LedgerFilters } from '@withpotter/lib/admin-api';\nimport type { TenantListParams } from '@withpotter/lib/admin-tenants-query';\nimport type { AccountStage } from '@withpotter/lib/admin-account-types';\n\nexport function useAdminSession(enabled = true) {\n return useQuery({\n queryKey: ['platform-admin', 'session'],\n queryFn: () => platformAdminApi.me(),\n enabled,\n retry: false,\n staleTime: 60_000,\n });\n}\n\nexport function usePlatformOverview() {\n return useQuery({\n queryKey: ['platform-admin', 'overview'],\n queryFn: () => platformAdminApi.overview(),\n });\n}\n\nexport function usePlatformCockpit(\n range: { startDate?: string; endDate?: string } = {},\n) {\n return useQuery({\n queryKey: ['platform-admin', 'cockpit', range],\n queryFn: () => platformAdminApi.cockpit(range),\n });\n}\n\nexport function usePlatformRevenue(\n range: { startDate?: string; endDate?: string } = {},\n) {\n return useQuery({\n queryKey: ['platform-admin', 'revenue', range],\n queryFn: () => platformAdminApi.revenue(range),\n });\n}\n\n/**\n * How customers paid, per channel. Not every box can say: where the route is\n * absent the query fails once (no retry) and `data` stays undefined, which the\n * panels read as \"nothing to show\".\n */\nexport function useGatewayChannels(\n range: { startDate?: string; endDate?: string } = {},\n) {\n return useQuery({\n queryKey: ['platform-admin', 'gateway-channels', range],\n queryFn: () => platformAdminApi.gatewayChannels(range),\n retry: false,\n staleTime: 60_000,\n });\n}\n\n/**\n * Revenue's Merchant payments view. A box without the route answers 404; the\n * view reads that as \"not available here\", never as a failure to retry.\n */\nexport function useMerchantPayments(filters: LedgerFilters) {\n return useQuery({\n queryKey: ['platform-admin', 'revenue', 'merchant-payments', filters],\n queryFn: () => platformAdminApi.merchantPayments(filters),\n retry: false,\n staleTime: 30_000,\n placeholderData: keepPreviousData,\n });\n}\n\n/** Revenue's Subscriptions view: every plan payment merchants made to Olive. */\nexport function useSubscriptionPayments(filters: LedgerFilters) {\n return useQuery({\n queryKey: ['platform-admin', 'revenue', 'subscription-payments', filters],\n queryFn: () => platformAdminApi.subscriptionPayments(filters),\n retry: false,\n staleTime: 30_000,\n placeholderData: keepPreviousData,\n });\n}\n\n/** Revenue's Merchants view: what each merchant received and how customers paid them. */\nexport function useMerchantRevenue(filters: LedgerFilters) {\n return useQuery({\n queryKey: ['platform-admin', 'revenue', 'merchants', filters],\n queryFn: () => platformAdminApi.merchantRevenue(filters),\n retry: false,\n staleTime: 30_000,\n placeholderData: keepPreviousData,\n });\n}\n\n/** The merchants that took payments in the window, for the merchant picker. */\nexport function useMerchantOptions(range: { startDate?: string; endDate?: string }) {\n return useQuery({\n queryKey: ['platform-admin', 'revenue', 'merchant-options', range],\n queryFn: () => platformAdminApi.merchantOptions(range),\n retry: false,\n staleTime: 60_000,\n });\n}\n\nexport function usePlatformTenantsAnalytics(\n range: { startDate?: string; endDate?: string } = {},\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenants-analytics', range],\n queryFn: () => platformAdminApi.tenantsAnalytics(range),\n });\n}\n\nexport function usePlatformTenants(params: TenantListParams) {\n return useQuery({\n queryKey: ['platform-admin', 'tenants', params],\n queryFn: () => platformAdminApi.tenants(params),\n });\n}\n\nexport function usePlatformTenant(\n id: string,\n range: { startDate?: string; endDate?: string } = {},\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant', id, range],\n queryFn: () => platformAdminApi.tenant(id, range),\n enabled: !!id,\n });\n}\n\nexport function useTenantAnalytics(\n id: string,\n range: { startDate?: string; endDate?: string } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-analytics', id, range],\n queryFn: () => platformAdminApi.tenantAnalytics(id, range),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantFinance(\n id: string,\n range: { startDate?: string; endDate?: string } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-finance', id, range],\n queryFn: () => platformAdminApi.tenantFinance(id, range),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantTransactions(\n id: string,\n params: {\n status?: string;\n type?: string;\n search?: string;\n page?: number;\n limit?: number;\n } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-transactions', id, params],\n queryFn: () => platformAdminApi.tenantTransactions(id, params),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantCustomers(\n id: string,\n params: {\n search?: string;\n type?: string;\n status?: string;\n page?: number;\n limit?: number;\n } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-customers', id, params],\n queryFn: () => platformAdminApi.tenantCustomers(id, params),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantProducts(\n id: string,\n params: { type?: string; search?: string } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-products', id, params],\n queryFn: () => platformAdminApi.tenantProducts(id, params),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantProductInsights(\n id: string,\n range: { startDate?: string } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-product-insights', id, range],\n queryFn: () => platformAdminApi.tenantProductInsights(id, range),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantCustomerInsights(\n id: string,\n range: { startDate?: string } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-customer-insights', id, range],\n queryFn: () => platformAdminApi.tenantCustomerInsights(id, range),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantInvoices(\n id: string,\n params: {\n status?: string;\n search?: string;\n page?: number;\n limit?: number;\n } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-invoices', id, params],\n queryFn: () => platformAdminApi.tenantInvoices(id, params),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantLoyalty(id: string, enabled = true) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-loyalty', id],\n queryFn: () => platformAdminApi.tenantLoyalty(id),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantDiscounts(\n id: string,\n params: {\n status?: string;\n discountType?: string;\n search?: string;\n page?: number;\n limit?: number;\n } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-discounts', id, params],\n queryFn: () => platformAdminApi.tenantDiscounts(id, params),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantSettlements(\n id: string,\n params: {\n page?: number;\n limit?: number;\n startDate?: string;\n endDate?: string;\n } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-settlements', id, params],\n queryFn: () => platformAdminApi.tenantSettlements(id, params),\n enabled: !!id && enabled,\n });\n}\n\nexport function useTenantSubscriptions(\n id: string,\n params: {\n status?: string;\n planId?: string;\n page?: number;\n limit?: number;\n } = {},\n enabled = true,\n) {\n return useQuery({\n queryKey: ['platform-admin', 'tenant-subscriptions', id, params],\n queryFn: () => platformAdminApi.tenantSubscriptions(id, params),\n enabled: !!id && enabled,\n });\n}\n\nexport function usePlatformAccounts(params: {\n page?: number;\n pageSize?: number;\n search?: string;\n joinedFrom?: string;\n joinedTo?: string;\n stage?: AccountStage;\n}) {\n return useQuery({\n queryKey: ['platform-admin', 'accounts', params],\n queryFn: () => platformAdminApi.accounts(params),\n });\n}\n\nexport function usePlatformAccountsAnalytics(\n range: { startDate?: string; endDate?: string } = {},\n) {\n return useQuery({\n queryKey: ['platform-admin', 'accounts-analytics', range],\n queryFn: () => platformAdminApi.accountsAnalytics(range),\n });\n}\n\nexport function usePlatformAdmins() {\n return useQuery({\n queryKey: ['platform-admin', 'admins'],\n queryFn: () => platformAdminApi.admins(),\n });\n}\n\nexport function usePlatformAdminsAnalytics(\n range: { startDate?: string; endDate?: string } = {},\n) {\n return useQuery({\n queryKey: ['platform-admin', 'admins-analytics', range],\n queryFn: () => platformAdminApi.adminsAnalytics(range),\n });\n}\n\nexport function useAdminPermissions() {\n return useQuery({\n queryKey: ['platform-admin', 'admin-permissions'],\n queryFn: () => platformAdminApi.adminPermissions(),\n staleTime: 5 * 60_000,\n });\n}\n\nexport function useAdminRoles() {\n return useQuery({\n queryKey: ['platform-admin', 'roles'],\n queryFn: () => platformAdminApi.roles(),\n });\n}\n\nexport function useAccountSessions(id: string | null) {\n return useQuery({\n queryKey: ['platform-admin', 'account-sessions', id],\n queryFn: () => platformAdminApi.accountSessions(id!),\n enabled: !!id,\n });\n}\n\nexport function useAccountTenants(id: string | null) {\n return useQuery({\n queryKey: ['platform-admin', 'account-tenants', id],\n queryFn: () => platformAdminApi.accountTenants(id!),\n enabled: !!id,\n });\n}\n\nexport function usePlatformAudit(\n params: {\n page?: number;\n pageSize?: number;\n search?: string;\n action?: string;\n adminEmail?: string;\n from?: string;\n to?: string;\n } = {},\n) {\n return useQuery({\n queryKey: ['platform-admin', 'audit', params],\n queryFn: () => platformAdminApi.audit(params),\n });\n}\n\nexport function usePlatformAuditAnalytics(\n range: { startDate?: string; endDate?: string } = {},\n) {\n return useQuery({\n queryKey: ['platform-admin', 'audit-analytics', range],\n queryFn: () => platformAdminApi.auditAnalytics(range),\n });\n}\n"],"mappings":"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","names":[]}
@@ -2,7 +2,9 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useState } from "react";
4
4
  import { Button, RemovableChip } from "@withpotter/ui-kit";
5
- import { POS_PAGE_SIZE } from "@withpotter/lib/admin-pos-query";
5
+ import {
6
+ POS_PAGE_SIZE
7
+ } from "@withpotter/lib/admin-pos-query";
6
8
  import { FilterPress, FilterSelect, Filters } from "../_ui";
7
9
  import { ListPager } from "../_ui/list-card";
8
10
  import { LONG_OPEN_HOURS } from "../_pos/pos-rules";
@@ -17,8 +19,13 @@ const STATUS_OPTIONS = [
17
19
  label
18
20
  }))
19
21
  ];
20
- function ShiftsTab({ currency = "" }) {
21
- const [tenantId, setTenantId] = useTenantParam();
22
+ function ShiftsTab({
23
+ currency = "",
24
+ tenantId: fixedTenantId,
25
+ title = "Shifts"
26
+ }) {
27
+ const [paramTenantId, setTenantId] = useTenantParam();
28
+ const tenantId = fixedTenantId ?? paramTenantId;
22
29
  const [status, setStatus] = useState("");
23
30
  const [longOpen, setLongOpen] = useState(false);
24
31
  const [page, setPage] = useState(1);
@@ -30,7 +37,7 @@ function ShiftsTab({ currency = "" }) {
30
37
  limit: POS_PAGE_SIZE
31
38
  };
32
39
  const { data, isLoading, isError, error, refetch } = usePosShifts(filters);
33
- const narrowed = !!(status || longOpen || tenantId);
40
+ const narrowed = !!(status || longOpen || tenantId && !fixedTenantId);
34
41
  const reset = (fn) => {
35
42
  fn();
36
43
  setPage(1);
@@ -38,10 +45,10 @@ function ShiftsTab({ currency = "" }) {
38
45
  return /* @__PURE__ */ jsx(
39
46
  ShiftsTable,
40
47
  {
41
- title: "Shifts",
48
+ title,
42
49
  rows: data?.data,
43
50
  total: data?.total,
44
- withBusiness: true,
51
+ withBusiness: !fixedTenantId,
45
52
  currency,
46
53
  isLoading,
47
54
  isError,
@@ -49,7 +56,7 @@ function ShiftsTab({ currency = "" }) {
49
56
  onRetry: () => void refetch(),
50
57
  emptyTitle: narrowed ? "No shifts match." : "No shifts yet.",
51
58
  controls: /* @__PURE__ */ jsxs(Filters, { label: "Filter shifts", children: [
52
- tenantId && /* @__PURE__ */ jsx(
59
+ tenantId && !fixedTenantId && /* @__PURE__ */ jsx(
53
60
  RemovableChip,
54
61
  {
55
62
  onRemove: () => reset(() => setTenantId("")),
@@ -86,7 +93,7 @@ function ShiftsTab({ currency = "" }) {
86
93
  onClick: () => reset(() => {
87
94
  setStatus("");
88
95
  setLongOpen(false);
89
- setTenantId("");
96
+ if (!fixedTenantId) setTenantId("");
90
97
  }),
91
98
  children: "Clear filters"
92
99
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/pos/shifts-tab.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { Button, RemovableChip } from '@withpotter/ui-kit';\nimport { POS_PAGE_SIZE, type ShiftFilters } from '@withpotter/lib/admin-pos-query';\nimport type { PosShiftStatus } from '@withpotter/lib/admin-pos-types';\nimport { FilterPress, FilterSelect, Filters } from '../_ui';\nimport { ListPager } from '../_ui/list-card';\nimport { LONG_OPEN_HOURS } from '../_pos/pos-rules';\nimport { SHIFT_STATUS_LABEL } from '../_pos/pos-words';\nimport { ShiftsTable } from '../_pos/shifts-table';\nimport { usePosShifts } from '../_pos/use-pos';\nimport { useTenantParam } from '../_pos/use-tenant-param';\n\nconst STATUS_OPTIONS = [\n { value: '', label: 'Any status' },\n ...Object.entries(SHIFT_STATUS_LABEL).map(([value, label]) => ({\n value,\n label,\n })),\n];\n\n/** Open and recent shifts across every business, newest first. */\nexport function ShiftsTab({ currency = '' }: { currency?: string }) {\n const [tenantId, setTenantId] = useTenantParam();\n const [status, setStatus] = useState<PosShiftStatus | ''>('');\n const [longOpen, setLongOpen] = useState(false);\n const [page, setPage] = useState(1);\n const filters: ShiftFilters = {\n status: longOpen ? 'open' : status,\n openOverHours: longOpen ? LONG_OPEN_HOURS : undefined,\n tenantId: tenantId || undefined,\n page,\n limit: POS_PAGE_SIZE,\n };\n const { data, isLoading, isError, error, refetch } = usePosShifts(filters);\n const narrowed = !!(status || longOpen || tenantId);\n const reset = (fn: () => void) => {\n fn();\n setPage(1);\n };\n\n return (\n <ShiftsTable\n title=\"Shifts\"\n rows={data?.data}\n total={data?.total}\n withBusiness\n currency={currency}\n isLoading={isLoading}\n isError={isError}\n error={error}\n onRetry={() => void refetch()}\n emptyTitle={narrowed ? 'No shifts match.' : 'No shifts yet.'}\n controls={\n <Filters label=\"Filter shifts\">\n {tenantId && (\n <RemovableChip\n onRemove={() => reset(() => setTenantId(''))}\n removeLabel=\"Show every business\"\n >\n One business\n </RemovableChip>\n )}\n <FilterPress\n on={longOpen}\n onToggle={() => reset(() => setLongOpen((v) => !v))}\n >\n Open over {LONG_OPEN_HOURS} hours\n </FilterPress>\n {!longOpen && (\n <FilterSelect\n label=\"Status\"\n value={status}\n onChange={(v) => reset(() => setStatus(v as PosShiftStatus | ''))}\n options={STATUS_OPTIONS}\n />\n )}\n {narrowed && (\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onClick={() =>\n reset(() => {\n setStatus('');\n setLongOpen(false);\n setTenantId('');\n })\n }\n >\n Clear filters\n </Button>\n )}\n </Filters>\n }\n footer={\n data && data.total > 0 ? (\n <ListPager\n page={page}\n totalPages={Math.max(1, data.totalPages)}\n total={data.total}\n noun={data.total === 1 ? 'shift' : 'shifts'}\n onPage={setPage}\n />\n ) : undefined\n }\n />\n );\n}\n"],"mappings":";AAyDY,cAOF,YAPE;AAvDZ,SAAS,gBAAgB;AACzB,SAAS,QAAQ,qBAAqB;AACtC,SAAS,qBAAwC;AAEjD,SAAS,aAAa,cAAc,eAAe;AACnD,SAAS,iBAAiB;AAC1B,SAAS,uBAAuB;AAChC,SAAS,0BAA0B;AACnC,SAAS,mBAAmB;AAC5B,SAAS,oBAAoB;AAC7B,SAAS,sBAAsB;AAE/B,MAAM,iBAAiB;AAAA,EACrB,EAAE,OAAO,IAAI,OAAO,aAAa;AAAA,EACjC,GAAG,OAAO,QAAQ,kBAAkB,EAAE,IAAI,CAAC,CAAC,OAAO,KAAK,OAAO;AAAA,IAC7D;AAAA,IACA;AAAA,EACF,EAAE;AACJ;AAGO,SAAS,UAAU,EAAE,WAAW,GAAG,GAA0B;AAClE,QAAM,CAAC,UAAU,WAAW,IAAI,eAAe;AAC/C,QAAM,CAAC,QAAQ,SAAS,IAAI,SAA8B,EAAE;AAC5D,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAC9C,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,CAAC;AAClC,QAAM,UAAwB;AAAA,IAC5B,QAAQ,WAAW,SAAS;AAAA,IAC5B,eAAe,WAAW,kBAAkB;AAAA,IAC5C,UAAU,YAAY;AAAA,IACtB;AAAA,IACA,OAAO;AAAA,EACT;AACA,QAAM,EAAE,MAAM,WAAW,SAAS,OAAO,QAAQ,IAAI,aAAa,OAAO;AACzE,QAAM,WAAW,CAAC,EAAE,UAAU,YAAY;AAC1C,QAAM,QAAQ,CAAC,OAAmB;AAChC,OAAG;AACH,YAAQ,CAAC;AAAA,EACX;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAM;AAAA,MACN,MAAM,MAAM;AAAA,MACZ,OAAO,MAAM;AAAA,MACb,cAAY;AAAA,MACZ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,SAAS,MAAM,KAAK,QAAQ;AAAA,MAC5B,YAAY,WAAW,qBAAqB;AAAA,MAC5C,UACE,qBAAC,WAAQ,OAAM,iBACZ;AAAA,oBACC;AAAA,UAAC;AAAA;AAAA,YACC,UAAU,MAAM,MAAM,MAAM,YAAY,EAAE,CAAC;AAAA,YAC3C,aAAY;AAAA,YACb;AAAA;AAAA,QAED;AAAA,QAEF;AAAA,UAAC;AAAA;AAAA,YACC,IAAI;AAAA,YACJ,UAAU,MAAM,MAAM,MAAM,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC;AAAA,YACnD;AAAA;AAAA,cACY;AAAA,cAAgB;AAAA;AAAA;AAAA,QAC7B;AAAA,QACC,CAAC,YACA;AAAA,UAAC;AAAA;AAAA,YACC,OAAM;AAAA,YACN,OAAO;AAAA,YACP,UAAU,CAAC,MAAM,MAAM,MAAM,UAAU,CAAwB,CAAC;AAAA,YAChE,SAAS;AAAA;AAAA,QACX;AAAA,QAED,YACC;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAQ;AAAA,YACR,SAAS,MACP,MAAM,MAAM;AACV,wBAAU,EAAE;AACZ,0BAAY,KAAK;AACjB,0BAAY,EAAE;AAAA,YAChB,CAAC;AAAA,YAEJ;AAAA;AAAA,QAED;AAAA,SAEJ;AAAA,MAEF,QACE,QAAQ,KAAK,QAAQ,IACnB;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA,YAAY,KAAK,IAAI,GAAG,KAAK,UAAU;AAAA,UACvC,OAAO,KAAK;AAAA,UACZ,MAAM,KAAK,UAAU,IAAI,UAAU;AAAA,UACnC,QAAQ;AAAA;AAAA,MACV,IACE;AAAA;AAAA,EAER;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/pos/shifts-tab.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { Button, RemovableChip } from '@withpotter/ui-kit';\nimport {\n POS_PAGE_SIZE,\n type ShiftFilters,\n} from '@withpotter/lib/admin-pos-query';\nimport type { PosShiftStatus } from '@withpotter/lib/admin-pos-types';\nimport { FilterPress, FilterSelect, Filters } from '../_ui';\nimport { ListPager } from '../_ui/list-card';\nimport { LONG_OPEN_HOURS } from '../_pos/pos-rules';\nimport { SHIFT_STATUS_LABEL } from '../_pos/pos-words';\nimport { ShiftsTable } from '../_pos/shifts-table';\nimport { usePosShifts } from '../_pos/use-pos';\nimport { useTenantParam } from '../_pos/use-tenant-param';\n\nconst STATUS_OPTIONS = [\n { value: '', label: 'Any status' },\n ...Object.entries(SHIFT_STATUS_LABEL).map(([value, label]) => ({\n value,\n label,\n })),\n];\n\n/** Open and recent shifts across every business, newest first. */\nexport function ShiftsTab({\n currency = '',\n tenantId: fixedTenantId,\n title = 'Shifts',\n}: {\n currency?: string;\n /** Fixes the list to one business (its own page); otherwise `?tenant=` narrows it. */\n tenantId?: string;\n title?: string;\n}) {\n const [paramTenantId, setTenantId] = useTenantParam();\n const tenantId = fixedTenantId ?? paramTenantId;\n const [status, setStatus] = useState<PosShiftStatus | ''>('');\n const [longOpen, setLongOpen] = useState(false);\n const [page, setPage] = useState(1);\n const filters: ShiftFilters = {\n status: longOpen ? 'open' : status,\n openOverHours: longOpen ? LONG_OPEN_HOURS : undefined,\n tenantId: tenantId || undefined,\n page,\n limit: POS_PAGE_SIZE,\n };\n const { data, isLoading, isError, error, refetch } = usePosShifts(filters);\n const narrowed = !!(status || longOpen || (tenantId && !fixedTenantId));\n const reset = (fn: () => void) => {\n fn();\n setPage(1);\n };\n\n return (\n <ShiftsTable\n title={title}\n rows={data?.data}\n total={data?.total}\n withBusiness={!fixedTenantId}\n currency={currency}\n isLoading={isLoading}\n isError={isError}\n error={error}\n onRetry={() => void refetch()}\n emptyTitle={narrowed ? 'No shifts match.' : 'No shifts yet.'}\n controls={\n <Filters label=\"Filter shifts\">\n {tenantId && !fixedTenantId && (\n <RemovableChip\n onRemove={() => reset(() => setTenantId(''))}\n removeLabel=\"Show every business\"\n >\n One business\n </RemovableChip>\n )}\n <FilterPress\n on={longOpen}\n onToggle={() => reset(() => setLongOpen((v) => !v))}\n >\n Open over {LONG_OPEN_HOURS} hours\n </FilterPress>\n {!longOpen && (\n <FilterSelect\n label=\"Status\"\n value={status}\n onChange={(v) => reset(() => setStatus(v as PosShiftStatus | ''))}\n options={STATUS_OPTIONS}\n />\n )}\n {narrowed && (\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onClick={() =>\n reset(() => {\n setStatus('');\n setLongOpen(false);\n if (!fixedTenantId) setTenantId('');\n })\n }\n >\n Clear filters\n </Button>\n )}\n </Filters>\n }\n footer={\n data && data.total > 0 ? (\n <ListPager\n page={page}\n totalPages={Math.max(1, data.totalPages)}\n total={data.total}\n noun={data.total === 1 ? 'shift' : 'shifts'}\n onPage={setPage}\n />\n ) : undefined\n }\n />\n );\n}\n"],"mappings":"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","names":[]}
@@ -17,7 +17,7 @@ function CohortPanel({ cohorts }) {
17
17
  return /* @__PURE__ */ jsx(
18
18
  Panel,
19
19
  {
20
- title: "Logo cohort retention",
20
+ title: "Do new merchants keep selling?",
21
21
  action: cohorts.length > 0 && /* @__PURE__ */ jsx(
22
22
  ViewSwitch,
23
23
  {
@@ -28,17 +28,17 @@ function CohortPanel({ cohorts }) {
28
28
  onChange: setLens
29
29
  }
30
30
  ),
31
- footer: "Share of each signup-month cohort still transacting (at least 1 paid order) at each month offset. M0 is the signup month itself.",
31
+ footer: "Of the merchants who signed up in each month, the share still selling (at least one paid order) in each month after. M0 is the sign-up month itself.",
32
32
  flush: true,
33
- children: cohorts.length === 0 ? /* @__PURE__ */ jsx(EmptyNote, { className: styles.empty, children: "Not enough signup history yet." }) : /* @__PURE__ */ jsx(
33
+ children: cohorts.length === 0 ? /* @__PURE__ */ jsx(EmptyNote, { className: styles.empty, children: "Not enough sign-up history yet." }) : /* @__PURE__ */ jsx(
34
34
  TableShell,
35
35
  {
36
36
  bare: true,
37
37
  minWidth: Math.max(320, 200 + offsets.length * 64),
38
38
  density: "compact",
39
39
  head: /* @__PURE__ */ jsxs("tr", { children: [
40
- /* @__PURE__ */ jsx(Th, { children: "Cohort" }),
41
- /* @__PURE__ */ jsx(Th, { align: "right", children: "Tenants" }),
40
+ /* @__PURE__ */ jsx(Th, { children: "Signed up" }),
41
+ /* @__PURE__ */ jsx(Th, { align: "right", children: "Merchants" }),
42
42
  offsets.map((_, i) => /* @__PURE__ */ jsxs(Th, { align: "right", children: [
43
43
  "M",
44
44
  i
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/revenue/cohort-panel.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { EmptyNote, TableShell, Th, ViewSwitch } from '@withpotter/ui-kit';\nimport type { PlatformRevenue } from '@withpotter/lib/admin-api';\nimport { TONE } from '../_fx';\nimport { Panel } from '../_ui';\nimport styles from './revenue.module.css';\n\nconst integer = new Intl.NumberFormat('en-US');\n\ntype Cohort = NonNullable<PlatformRevenue['quality']>['cohorts'][number];\ntype Lens = 'share' | 'count';\n\nconst LENSES = [\n { value: 'share' as const, label: 'Share' },\n { value: 'count' as const, label: 'Merchants' },\n];\n\n/**\n * Logo retention by signup month, shaded by the share still transacting.\n * The switch reads each cell as a share of the cohort or as the merchants\n * that share stands for; the shading stays the share either way.\n */\nexport function CohortPanel({ cohorts }: { cohorts: Cohort[] }) {\n const [lens, setLens] = useState<Lens>('share');\n const offsets = cohorts[0]?.retention ?? [];\n const cell = (v: number, size: number) =>\n lens === 'share' ? `${v}%` : integer.format(Math.round((size * v) / 100));\n\n return (\n <Panel\n title=\"Logo cohort retention\"\n action={\n cohorts.length > 0 && (\n <ViewSwitch\n className=\"adm-view-switch\"\n aria-label=\"Show cohort cells as\"\n options={LENSES}\n value={lens}\n onChange={setLens}\n />\n )\n }\n footer=\"Share of each signup-month cohort still transacting (at least 1 paid order) at each month offset. M0 is the signup month itself.\"\n flush\n >\n {cohorts.length === 0 ? (\n <EmptyNote className={styles.empty}>\n Not enough signup history yet.\n </EmptyNote>\n ) : (\n <TableShell\n bare\n minWidth={Math.max(320, 200 + offsets.length * 64)}\n density=\"compact\"\n head={\n <tr>\n <Th>Cohort</Th>\n <Th align=\"right\">Tenants</Th>\n {offsets.map((_, i) => (\n <Th key={i} align=\"right\">\n M{i}\n </Th>\n ))}\n </tr>\n }\n >\n {cohorts.map((c) => (\n <tr key={c.cohort}>\n <td className={styles.cohort}>{c.cohort}</td>\n <td className={styles.num}>{integer.format(c.size)}</td>\n {c.retention.map((v, i) => (\n <td\n key={i}\n className={styles.heat}\n data-empty={v == null}\n style={\n v == null\n ? undefined\n : {\n // 60% at most, so dark figures stay readable on the darkest cell.\n background: `color-mix(in srgb, ${TONE.brand.fg} ${Math.round(\n Math.min(v, 100) * 0.6,\n )}%, transparent)`,\n }\n }\n >\n {v == null ? '–' : cell(v, c.size)}\n </td>\n ))}\n </tr>\n ))}\n </TableShell>\n )}\n </Panel>\n );\n}\n"],"mappings":";AAmCU,cA0BM,YA1BN;AAjCV,SAAS,gBAAgB;AACzB,SAAS,WAAW,YAAY,IAAI,kBAAkB;AAEtD,SAAS,YAAY;AACrB,SAAS,aAAa;AACtB,OAAO,YAAY;AAEnB,MAAM,UAAU,IAAI,KAAK,aAAa,OAAO;AAK7C,MAAM,SAAS;AAAA,EACb,EAAE,OAAO,SAAkB,OAAO,QAAQ;AAAA,EAC1C,EAAE,OAAO,SAAkB,OAAO,YAAY;AAChD;AAOO,SAAS,YAAY,EAAE,QAAQ,GAA0B;AAC9D,QAAM,CAAC,MAAM,OAAO,IAAI,SAAe,OAAO;AAC9C,QAAM,UAAU,QAAQ,CAAC,GAAG,aAAa,CAAC;AAC1C,QAAM,OAAO,CAAC,GAAW,SACvB,SAAS,UAAU,GAAG,CAAC,MAAM,QAAQ,OAAO,KAAK,MAAO,OAAO,IAAK,GAAG,CAAC;AAE1E,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAM;AAAA,MACN,QACE,QAAQ,SAAS,KACf;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,cAAW;AAAA,UACX,SAAS;AAAA,UACT,OAAO;AAAA,UACP,UAAU;AAAA;AAAA,MACZ;AAAA,MAGJ,QAAO;AAAA,MACP,OAAK;AAAA,MAEJ,kBAAQ,WAAW,IAClB,oBAAC,aAAU,WAAW,OAAO,OAAO,4CAEpC,IAEA;AAAA,QAAC;AAAA;AAAA,UACC,MAAI;AAAA,UACJ,UAAU,KAAK,IAAI,KAAK,MAAM,QAAQ,SAAS,EAAE;AAAA,UACjD,SAAQ;AAAA,UACR,MACE,qBAAC,QACC;AAAA,gCAAC,MAAG,oBAAM;AAAA,YACV,oBAAC,MAAG,OAAM,SAAQ,qBAAO;AAAA,YACxB,QAAQ,IAAI,CAAC,GAAG,MACf,qBAAC,MAAW,OAAM,SAAQ;AAAA;AAAA,cACtB;AAAA,iBADK,CAET,CACD;AAAA,aACH;AAAA,UAGD,kBAAQ,IAAI,CAAC,MACZ,qBAAC,QACC;AAAA,gCAAC,QAAG,WAAW,OAAO,QAAS,YAAE,QAAO;AAAA,YACxC,oBAAC,QAAG,WAAW,OAAO,KAAM,kBAAQ,OAAO,EAAE,IAAI,GAAE;AAAA,YAClD,EAAE,UAAU,IAAI,CAAC,GAAG,MACnB;AAAA,cAAC;AAAA;AAAA,gBAEC,WAAW,OAAO;AAAA,gBAClB,cAAY,KAAK;AAAA,gBACjB,OACE,KAAK,OACD,SACA;AAAA;AAAA,kBAEE,YAAY,sBAAsB,KAAK,MAAM,EAAE,IAAI,KAAK;AAAA,oBACtD,KAAK,IAAI,GAAG,GAAG,IAAI;AAAA,kBACrB,CAAC;AAAA,gBACH;AAAA,gBAGL,eAAK,OAAO,WAAM,KAAK,GAAG,EAAE,IAAI;AAAA;AAAA,cAd5B;AAAA,YAeP,CACD;AAAA,eArBM,EAAE,MAsBX,CACD;AAAA;AAAA,MACH;AAAA;AAAA,EAEJ;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/revenue/cohort-panel.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { EmptyNote, TableShell, Th, ViewSwitch } from '@withpotter/ui-kit';\nimport type { PlatformRevenue } from '@withpotter/lib/admin-api';\nimport { TONE } from '../_fx';\nimport { Panel } from '../_ui';\nimport styles from './revenue.module.css';\n\nconst integer = new Intl.NumberFormat('en-US');\n\ntype Cohort = NonNullable<PlatformRevenue['quality']>['cohorts'][number];\ntype Lens = 'share' | 'count';\n\nconst LENSES = [\n { value: 'share' as const, label: 'Share' },\n { value: 'count' as const, label: 'Merchants' },\n];\n\n/**\n * Logo retention by signup month, shaded by the share still transacting.\n * The switch reads each cell as a share of the cohort or as the merchants\n * that share stands for; the shading stays the share either way.\n */\nexport function CohortPanel({ cohorts }: { cohorts: Cohort[] }) {\n const [lens, setLens] = useState<Lens>('share');\n const offsets = cohorts[0]?.retention ?? [];\n const cell = (v: number, size: number) =>\n lens === 'share' ? `${v}%` : integer.format(Math.round((size * v) / 100));\n\n return (\n <Panel\n title=\"Do new merchants keep selling?\"\n action={\n cohorts.length > 0 && (\n <ViewSwitch\n className=\"adm-view-switch\"\n aria-label=\"Show cohort cells as\"\n options={LENSES}\n value={lens}\n onChange={setLens}\n />\n )\n }\n footer=\"Of the merchants who signed up in each month, the share still selling (at least one paid order) in each month after. M0 is the sign-up month itself.\"\n flush\n >\n {cohorts.length === 0 ? (\n <EmptyNote className={styles.empty}>\n Not enough sign-up history yet.\n </EmptyNote>\n ) : (\n <TableShell\n bare\n minWidth={Math.max(320, 200 + offsets.length * 64)}\n density=\"compact\"\n head={\n <tr>\n <Th>Signed up</Th>\n <Th align=\"right\">Merchants</Th>\n {offsets.map((_, i) => (\n <Th key={i} align=\"right\">\n M{i}\n </Th>\n ))}\n </tr>\n }\n >\n {cohorts.map((c) => (\n <tr key={c.cohort}>\n <td className={styles.cohort}>{c.cohort}</td>\n <td className={styles.num}>{integer.format(c.size)}</td>\n {c.retention.map((v, i) => (\n <td\n key={i}\n className={styles.heat}\n data-empty={v == null}\n style={\n v == null\n ? undefined\n : {\n // 60% at most, so dark figures stay readable on the darkest cell.\n background: `color-mix(in srgb, ${TONE.brand.fg} ${Math.round(\n Math.min(v, 100) * 0.6,\n )}%, transparent)`,\n }\n }\n >\n {v == null ? '–' : cell(v, c.size)}\n </td>\n ))}\n </tr>\n ))}\n </TableShell>\n )}\n </Panel>\n );\n}\n"],"mappings":";AAmCU,cA0BM,YA1BN;AAjCV,SAAS,gBAAgB;AACzB,SAAS,WAAW,YAAY,IAAI,kBAAkB;AAEtD,SAAS,YAAY;AACrB,SAAS,aAAa;AACtB,OAAO,YAAY;AAEnB,MAAM,UAAU,IAAI,KAAK,aAAa,OAAO;AAK7C,MAAM,SAAS;AAAA,EACb,EAAE,OAAO,SAAkB,OAAO,QAAQ;AAAA,EAC1C,EAAE,OAAO,SAAkB,OAAO,YAAY;AAChD;AAOO,SAAS,YAAY,EAAE,QAAQ,GAA0B;AAC9D,QAAM,CAAC,MAAM,OAAO,IAAI,SAAe,OAAO;AAC9C,QAAM,UAAU,QAAQ,CAAC,GAAG,aAAa,CAAC;AAC1C,QAAM,OAAO,CAAC,GAAW,SACvB,SAAS,UAAU,GAAG,CAAC,MAAM,QAAQ,OAAO,KAAK,MAAO,OAAO,IAAK,GAAG,CAAC;AAE1E,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAM;AAAA,MACN,QACE,QAAQ,SAAS,KACf;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,cAAW;AAAA,UACX,SAAS;AAAA,UACT,OAAO;AAAA,UACP,UAAU;AAAA;AAAA,MACZ;AAAA,MAGJ,QAAO;AAAA,MACP,OAAK;AAAA,MAEJ,kBAAQ,WAAW,IAClB,oBAAC,aAAU,WAAW,OAAO,OAAO,6CAEpC,IAEA;AAAA,QAAC;AAAA;AAAA,UACC,MAAI;AAAA,UACJ,UAAU,KAAK,IAAI,KAAK,MAAM,QAAQ,SAAS,EAAE;AAAA,UACjD,SAAQ;AAAA,UACR,MACE,qBAAC,QACC;AAAA,gCAAC,MAAG,uBAAS;AAAA,YACb,oBAAC,MAAG,OAAM,SAAQ,uBAAS;AAAA,YAC1B,QAAQ,IAAI,CAAC,GAAG,MACf,qBAAC,MAAW,OAAM,SAAQ;AAAA;AAAA,cACtB;AAAA,iBADK,CAET,CACD;AAAA,aACH;AAAA,UAGD,kBAAQ,IAAI,CAAC,MACZ,qBAAC,QACC;AAAA,gCAAC,QAAG,WAAW,OAAO,QAAS,YAAE,QAAO;AAAA,YACxC,oBAAC,QAAG,WAAW,OAAO,KAAM,kBAAQ,OAAO,EAAE,IAAI,GAAE;AAAA,YAClD,EAAE,UAAU,IAAI,CAAC,GAAG,MACnB;AAAA,cAAC;AAAA;AAAA,gBAEC,WAAW,OAAO;AAAA,gBAClB,cAAY,KAAK;AAAA,gBACjB,OACE,KAAK,OACD,SACA;AAAA;AAAA,kBAEE,YAAY,sBAAsB,KAAK,MAAM,EAAE,IAAI,KAAK;AAAA,oBACtD,KAAK,IAAI,GAAG,GAAG,IAAI;AAAA,kBACrB,CAAC;AAAA,gBACH;AAAA,gBAGL,eAAK,OAAO,WAAM,KAAK,GAAG,EAAE,IAAI;AAAA;AAAA,cAd5B;AAAA,YAeP,CACD;AAAA,eArBM,EAAE,MAsBX,CACD;AAAA;AAAA,MACH;AAAA;AAAA,EAEJ;AAEJ;","names":[]}
@@ -8,50 +8,50 @@ function HealthPanel({
8
8
  caption
9
9
  }) {
10
10
  if (!health) {
11
- return /* @__PURE__ */ jsx(Panel, { title: "Revenue health", caption, children: /* @__PURE__ */ jsx(AdmLoading, { label: "Loading revenue health\u2026", size: "sm" }) });
11
+ return /* @__PURE__ */ jsx(Panel, { title: "How plan income is changing", caption, children: /* @__PURE__ */ jsx(AdmLoading, { label: "Loading revenue health\u2026", size: "sm" }) });
12
12
  }
13
13
  const rows = [
14
14
  {
15
- label: "Net new MRR",
16
- note: "New and expansion, less churned",
15
+ label: "Plan income change",
16
+ note: "Gained from new merchants, less lost from merchants who left",
17
17
  value: money(health.netNewMrr),
18
- hint: `+${money(health.newMrr)} new \xB7 \u2212${money(health.churnedMrr)} churned`,
18
+ hint: `+${money(health.newMrr)} gained \xB7 \u2212${money(health.churnedMrr)} lost`,
19
19
  tone: health.netNewMrr < 0 ? "danger" : "default"
20
20
  },
21
21
  {
22
- label: "Gross revenue retention",
23
- note: "Share of starting MRR kept, ignoring expansion",
22
+ label: "Plan income kept",
23
+ note: "Share of the plan income at the start that is still being paid",
24
24
  value: `${health.grossRevenueRetentionPct}%`
25
25
  },
26
26
  {
27
- label: "Gross MRR churn",
28
- note: "MRR lost as a share of the base",
27
+ label: "Plan income lost",
28
+ note: "Share of plan income lost to merchants who left",
29
29
  value: `${health.grossMrrChurnPct}%`,
30
30
  tone: health.grossMrrChurnPct > 0 ? "danger" : "default"
31
31
  },
32
32
  {
33
- label: "Net new logos",
34
- note: "Paying merchants gained, less lost",
33
+ label: "Paying merchants gained",
34
+ note: "Merchants who started paying, less merchants who stopped",
35
35
  value: integer.format(health.netNewLogos),
36
- hint: `+${integer.format(health.newLogos)} new \xB7 \u2212${integer.format(
36
+ hint: `+${integer.format(health.newLogos)} joined \xB7 \u2212${integer.format(
37
37
  health.churnedLogos
38
- )} churned`,
38
+ )} left`,
39
39
  tone: health.netNewLogos < 0 ? "danger" : "default"
40
40
  },
41
41
  {
42
- label: "Logo churn",
43
- note: "Paying merchants lost as a share of the base",
42
+ label: "Paying merchants lost",
43
+ note: "Share of paying merchants who stopped paying",
44
44
  value: `${health.logoChurnPct}%`,
45
45
  tone: health.logoChurnPct > 0 ? "danger" : "default"
46
46
  },
47
47
  {
48
- label: "GMV concentration",
49
- note: "Share of GMV from the top 10 merchants",
48
+ label: "Reliance on the top 10",
49
+ note: "Share of merchant sales that comes from the 10 biggest merchants",
50
50
  value: `${health.gmvConcentrationTop10Pct}%`,
51
51
  tone: health.gmvConcentrationTop10Pct > 50 ? "danger" : "default"
52
52
  }
53
53
  ];
54
- return /* @__PURE__ */ jsx(Panel, { title: "Revenue health", caption, children: /* @__PURE__ */ jsx(FigureList, { rows }) });
54
+ return /* @__PURE__ */ jsx(Panel, { title: "How plan income is changing", caption, children: /* @__PURE__ */ jsx(FigureList, { rows }) });
55
55
  }
56
56
  export {
57
57
  HealthPanel
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/revenue/health-panel.tsx"],"sourcesContent":["import type { PlatformCockpit } from '@withpotter/lib/admin-api';\nimport { AdmLoading, Panel } from '../_ui';\nimport { FigureList, type Figure } from './figure-list';\n\nconst integer = new Intl.NumberFormat('en-US');\n\n/** MRR movement, logo churn and concentration risk — how healthy the base is. */\nexport function HealthPanel({\n health,\n money,\n caption,\n}: {\n health: PlatformCockpit['health'] | undefined;\n money: (value: number) => string;\n caption: string;\n}) {\n if (!health) {\n return (\n <Panel title=\"Revenue health\" caption={caption}>\n <AdmLoading label=\"Loading revenue health…\" size=\"sm\" />\n </Panel>\n );\n }\n\n const rows: Figure[] = [\n {\n label: 'Net new MRR',\n note: 'New and expansion, less churned',\n value: money(health.netNewMrr),\n hint: `+${money(health.newMrr)} new · −${money(health.churnedMrr)} churned`,\n tone: health.netNewMrr < 0 ? 'danger' : 'default',\n },\n {\n label: 'Gross revenue retention',\n note: 'Share of starting MRR kept, ignoring expansion',\n value: `${health.grossRevenueRetentionPct}%`,\n },\n {\n label: 'Gross MRR churn',\n note: 'MRR lost as a share of the base',\n value: `${health.grossMrrChurnPct}%`,\n tone: health.grossMrrChurnPct > 0 ? 'danger' : 'default',\n },\n {\n label: 'Net new logos',\n note: 'Paying merchants gained, less lost',\n value: integer.format(health.netNewLogos),\n hint: `+${integer.format(health.newLogos)} new · −${integer.format(\n health.churnedLogos,\n )} churned`,\n tone: health.netNewLogos < 0 ? 'danger' : 'default',\n },\n {\n label: 'Logo churn',\n note: 'Paying merchants lost as a share of the base',\n value: `${health.logoChurnPct}%`,\n tone: health.logoChurnPct > 0 ? 'danger' : 'default',\n },\n {\n label: 'GMV concentration',\n note: 'Share of GMV from the top 10 merchants',\n value: `${health.gmvConcentrationTop10Pct}%`,\n tone: health.gmvConcentrationTop10Pct > 50 ? 'danger' : 'default',\n },\n ];\n\n return (\n <Panel title=\"Revenue health\" caption={caption}>\n <FigureList rows={rows} />\n </Panel>\n );\n}\n"],"mappings":"AAmBQ;AAlBR,SAAS,YAAY,aAAa;AAClC,SAAS,kBAA+B;AAExC,MAAM,UAAU,IAAI,KAAK,aAAa,OAAO;AAGtC,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,MAAI,CAAC,QAAQ;AACX,WACE,oBAAC,SAAM,OAAM,kBAAiB,SAC5B,8BAAC,cAAW,OAAM,gCAA0B,MAAK,MAAK,GACxD;AAAA,EAEJ;AAEA,QAAM,OAAiB;AAAA,IACrB;AAAA,MACE,OAAO;AAAA,MACP,MAAM;AAAA,MACN,OAAO,MAAM,OAAO,SAAS;AAAA,MAC7B,MAAM,IAAI,MAAM,OAAO,MAAM,CAAC,mBAAW,MAAM,OAAO,UAAU,CAAC;AAAA,MACjE,MAAM,OAAO,YAAY,IAAI,WAAW;AAAA,IAC1C;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,MAAM;AAAA,MACN,OAAO,GAAG,OAAO,wBAAwB;AAAA,IAC3C;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,MAAM;AAAA,MACN,OAAO,GAAG,OAAO,gBAAgB;AAAA,MACjC,MAAM,OAAO,mBAAmB,IAAI,WAAW;AAAA,IACjD;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,MAAM;AAAA,MACN,OAAO,QAAQ,OAAO,OAAO,WAAW;AAAA,MACxC,MAAM,IAAI,QAAQ,OAAO,OAAO,QAAQ,CAAC,mBAAW,QAAQ;AAAA,QAC1D,OAAO;AAAA,MACT,CAAC;AAAA,MACD,MAAM,OAAO,cAAc,IAAI,WAAW;AAAA,IAC5C;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,MAAM;AAAA,MACN,OAAO,GAAG,OAAO,YAAY;AAAA,MAC7B,MAAM,OAAO,eAAe,IAAI,WAAW;AAAA,IAC7C;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,MAAM;AAAA,MACN,OAAO,GAAG,OAAO,wBAAwB;AAAA,MACzC,MAAM,OAAO,2BAA2B,KAAK,WAAW;AAAA,IAC1D;AAAA,EACF;AAEA,SACE,oBAAC,SAAM,OAAM,kBAAiB,SAC5B,8BAAC,cAAW,MAAY,GAC1B;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/revenue/health-panel.tsx"],"sourcesContent":["import type { PlatformCockpit } from '@withpotter/lib/admin-api';\nimport { AdmLoading, Panel } from '../_ui';\nimport { FigureList, type Figure } from './figure-list';\n\nconst integer = new Intl.NumberFormat('en-US');\n\n/** MRR movement, logo churn and concentration risk — how healthy the base is. */\nexport function HealthPanel({\n health,\n money,\n caption,\n}: {\n health: PlatformCockpit['health'] | undefined;\n money: (value: number) => string;\n caption: string;\n}) {\n if (!health) {\n return (\n <Panel title=\"How plan income is changing\" caption={caption}>\n <AdmLoading label=\"Loading revenue health…\" size=\"sm\" />\n </Panel>\n );\n }\n\n const rows: Figure[] = [\n {\n label: 'Plan income change',\n note: 'Gained from new merchants, less lost from merchants who left',\n value: money(health.netNewMrr),\n hint: `+${money(health.newMrr)} gained · −${money(health.churnedMrr)} lost`,\n tone: health.netNewMrr < 0 ? 'danger' : 'default',\n },\n {\n label: 'Plan income kept',\n note: 'Share of the plan income at the start that is still being paid',\n value: `${health.grossRevenueRetentionPct}%`,\n },\n {\n label: 'Plan income lost',\n note: 'Share of plan income lost to merchants who left',\n value: `${health.grossMrrChurnPct}%`,\n tone: health.grossMrrChurnPct > 0 ? 'danger' : 'default',\n },\n {\n label: 'Paying merchants gained',\n note: 'Merchants who started paying, less merchants who stopped',\n value: integer.format(health.netNewLogos),\n hint: `+${integer.format(health.newLogos)} joined · −${integer.format(\n health.churnedLogos,\n )} left`,\n tone: health.netNewLogos < 0 ? 'danger' : 'default',\n },\n {\n label: 'Paying merchants lost',\n note: 'Share of paying merchants who stopped paying',\n value: `${health.logoChurnPct}%`,\n tone: health.logoChurnPct > 0 ? 'danger' : 'default',\n },\n {\n label: 'Reliance on the top 10',\n note: 'Share of merchant sales that comes from the 10 biggest merchants',\n value: `${health.gmvConcentrationTop10Pct}%`,\n tone: health.gmvConcentrationTop10Pct > 50 ? 'danger' : 'default',\n },\n ];\n\n return (\n <Panel title=\"How plan income is changing\" caption={caption}>\n <FigureList rows={rows} />\n </Panel>\n );\n}\n"],"mappings":"AAmBQ;AAlBR,SAAS,YAAY,aAAa;AAClC,SAAS,kBAA+B;AAExC,MAAM,UAAU,IAAI,KAAK,aAAa,OAAO;AAGtC,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,MAAI,CAAC,QAAQ;AACX,WACE,oBAAC,SAAM,OAAM,+BAA8B,SACzC,8BAAC,cAAW,OAAM,gCAA0B,MAAK,MAAK,GACxD;AAAA,EAEJ;AAEA,QAAM,OAAiB;AAAA,IACrB;AAAA,MACE,OAAO;AAAA,MACP,MAAM;AAAA,MACN,OAAO,MAAM,OAAO,SAAS;AAAA,MAC7B,MAAM,IAAI,MAAM,OAAO,MAAM,CAAC,sBAAc,MAAM,OAAO,UAAU,CAAC;AAAA,MACpE,MAAM,OAAO,YAAY,IAAI,WAAW;AAAA,IAC1C;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,MAAM;AAAA,MACN,OAAO,GAAG,OAAO,wBAAwB;AAAA,IAC3C;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,MAAM;AAAA,MACN,OAAO,GAAG,OAAO,gBAAgB;AAAA,MACjC,MAAM,OAAO,mBAAmB,IAAI,WAAW;AAAA,IACjD;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,MAAM;AAAA,MACN,OAAO,QAAQ,OAAO,OAAO,WAAW;AAAA,MACxC,MAAM,IAAI,QAAQ,OAAO,OAAO,QAAQ,CAAC,sBAAc,QAAQ;AAAA,QAC7D,OAAO;AAAA,MACT,CAAC;AAAA,MACD,MAAM,OAAO,cAAc,IAAI,WAAW;AAAA,IAC5C;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,MAAM;AAAA,MACN,OAAO,GAAG,OAAO,YAAY;AAAA,MAC7B,MAAM,OAAO,eAAe,IAAI,WAAW;AAAA,IAC7C;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,MAAM;AAAA,MACN,OAAO,GAAG,OAAO,wBAAwB;AAAA,MACzC,MAAM,OAAO,2BAA2B,KAAK,WAAW;AAAA,IAC1D;AAAA,EACF;AAEA,SACE,oBAAC,SAAM,OAAM,+BAA8B,SACzC,8BAAC,cAAW,MAAY,GAC1B;AAEJ;","names":[]}