@withpotter/pages-admin 0.13.0-next.120 → 0.13.0-next.123

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 (35) hide show
  1. package/dist/admin/_ui/admin.css +7 -0
  2. package/dist/admin/_ui/stat-card.js +11 -2
  3. package/dist/admin/_ui/stat-card.js.map +1 -1
  4. package/dist/admin/admins/access-dialog.js +2 -7
  5. package/dist/admin/admins/access-dialog.js.map +1 -1
  6. package/dist/admin/admins/invite-dialog.js +0 -6
  7. package/dist/admin/admins/invite-dialog.js.map +1 -1
  8. package/dist/admin/hooks.js +0 -7
  9. package/dist/admin/hooks.js.map +1 -1
  10. package/dist/admin/revenue/earnings/earnings-colors.js +25 -0
  11. package/dist/admin/revenue/earnings/earnings-colors.js.map +1 -0
  12. package/dist/admin/revenue/earnings/earnings-legend.js +75 -0
  13. package/dist/admin/revenue/earnings/earnings-legend.js.map +1 -0
  14. package/dist/admin/revenue/earnings/earnings-summary.js +296 -0
  15. package/dist/admin/revenue/earnings/earnings-summary.js.map +1 -0
  16. package/dist/admin/revenue/earnings/earnings-table.js +143 -0
  17. package/dist/admin/revenue/earnings/earnings-table.js.map +1 -0
  18. package/dist/admin/revenue/earnings/earnings.module.css +201 -0
  19. package/dist/admin/revenue/earnings/use-earnings.js +27 -0
  20. package/dist/admin/revenue/earnings/use-earnings.js.map +1 -0
  21. package/dist/admin/revenue/health-panel.js +5 -4
  22. package/dist/admin/revenue/health-panel.js.map +1 -1
  23. package/dist/admin/revenue/revenue-kpis.js +1 -1
  24. package/dist/admin/revenue/revenue-kpis.js.map +1 -1
  25. package/dist/admin/revenue/revenue-overview.js +20 -118
  26. package/dist/admin/revenue/revenue-overview.js.map +1 -1
  27. package/dist/admin/revenue/revenue-workspace.js +4 -10
  28. package/dist/admin/revenue/revenue-workspace.js.map +1 -1
  29. package/dist/admin/roles/page.js +4 -14
  30. package/dist/admin/roles/page.js.map +1 -1
  31. package/dist/admin/roles/role-dialog.js +1 -2
  32. package/dist/admin/roles/role-dialog.js.map +1 -1
  33. package/package.json +4 -4
  34. package/dist/admin/roles/role-requests.js +0 -80
  35. package/dist/admin/roles/role-requests.js.map +0 -1
@@ -0,0 +1,201 @@
1
+ /* Earnings: a pie with its key, and the ranked list of where fees come from. */
2
+
3
+ .pie {
4
+ display: flex;
5
+ flex-wrap: wrap;
6
+ align-items: center;
7
+ justify-content: center;
8
+ gap: 20px;
9
+ padding: 4px 0;
10
+ }
11
+
12
+ .legend {
13
+ display: grid;
14
+ gap: 10px;
15
+ margin: 0;
16
+ padding: 0;
17
+ list-style: none;
18
+ flex: 1 1 200px;
19
+ min-width: 0;
20
+ }
21
+
22
+ .legendRow {
23
+ display: grid;
24
+ grid-template-columns: 12px minmax(0, 1fr) auto;
25
+ align-items: center;
26
+ gap: 10px;
27
+ }
28
+
29
+ .swatch {
30
+ width: 12px;
31
+ height: 12px;
32
+ border-radius: 4px;
33
+ }
34
+
35
+ .legendText {
36
+ display: grid;
37
+ min-width: 0;
38
+ }
39
+
40
+ .legendLabel {
41
+ font-size: 13px;
42
+ line-height: 18px;
43
+ color: var(--text-primary);
44
+ overflow: hidden;
45
+ text-overflow: ellipsis;
46
+ white-space: nowrap;
47
+ }
48
+
49
+ .legendDetail {
50
+ font-size: 12px;
51
+ line-height: 16px;
52
+ color: var(--text-tertiary);
53
+ }
54
+
55
+ .legendNumbers {
56
+ display: grid;
57
+ justify-items: end;
58
+ font-size: 12px;
59
+ line-height: 16px;
60
+ color: var(--text-tertiary);
61
+ font-variant-numeric: tabular-nums;
62
+ }
63
+
64
+ .legendNumbers strong {
65
+ font-size: 13px;
66
+ font-weight: 500;
67
+ color: var(--text-primary);
68
+ }
69
+
70
+ .rankList {
71
+ display: grid;
72
+ gap: 14px;
73
+ margin: 0;
74
+ padding: 0;
75
+ list-style: none;
76
+ }
77
+
78
+ .rankRow {
79
+ display: grid;
80
+ grid-template-columns: minmax(0, 1fr) auto;
81
+ gap: 4px 16px;
82
+ align-items: baseline;
83
+ font-size: 14px;
84
+ }
85
+
86
+ .rankName {
87
+ min-width: 0;
88
+ overflow: hidden;
89
+ text-overflow: ellipsis;
90
+ white-space: nowrap;
91
+ }
92
+
93
+ .rankSub {
94
+ grid-column: 1;
95
+ font-size: 12px;
96
+ color: var(--text-tertiary);
97
+ }
98
+
99
+ .rankAmount {
100
+ grid-row: 1;
101
+ grid-column: 2;
102
+ font-weight: 500;
103
+ font-variant-numeric: tabular-nums;
104
+ }
105
+
106
+ .rankBar {
107
+ grid-column: 1 / -1;
108
+ height: 6px;
109
+ border-radius: 999px;
110
+ background: var(--bg-subtle);
111
+ overflow: hidden;
112
+ }
113
+
114
+ .rankFill {
115
+ display: block;
116
+ height: 100%;
117
+ border-radius: 999px;
118
+ }
119
+
120
+ /* Stream against the period before: a faded bar for then, a solid one for now. */
121
+ .cmp {
122
+ display: grid;
123
+ gap: 16px;
124
+ margin: 0;
125
+ padding: 0;
126
+ list-style: none;
127
+ }
128
+
129
+ .cmpRow {
130
+ display: grid;
131
+ gap: 4px;
132
+ }
133
+
134
+ .cmpHead {
135
+ display: grid;
136
+ grid-template-columns: 12px minmax(0, 1fr) auto;
137
+ align-items: center;
138
+ gap: 10px;
139
+ }
140
+
141
+ .cmpDelta {
142
+ font-size: 12px;
143
+ font-weight: 500;
144
+ color: var(--text-tertiary);
145
+ font-variant-numeric: tabular-nums;
146
+ }
147
+
148
+ .up {
149
+ color: var(--success);
150
+ }
151
+
152
+ .down {
153
+ color: var(--error);
154
+ }
155
+
156
+ .cmpTrack {
157
+ display: block;
158
+ height: 8px;
159
+ border-radius: 999px;
160
+ background: var(--bg-subtle);
161
+ overflow: hidden;
162
+ }
163
+
164
+ .cmpPrev,
165
+ .cmpNow {
166
+ display: block;
167
+ height: 100%;
168
+ border-radius: 999px;
169
+ }
170
+
171
+ .cmpPrev {
172
+ opacity: 0.35;
173
+ }
174
+
175
+ .cmpNums {
176
+ display: flex;
177
+ justify-content: space-between;
178
+ gap: 12px;
179
+ font-size: 12px;
180
+ color: var(--text-tertiary);
181
+ font-variant-numeric: tabular-nums;
182
+ }
183
+
184
+ .cmpNums strong {
185
+ font-size: 13px;
186
+ font-weight: 500;
187
+ color: var(--text-primary);
188
+ }
189
+
190
+ /* A metric card that drives the chart: the chosen one is outlined. */
191
+ .metric {
192
+ cursor: pointer;
193
+ border-radius: var(--adm-card-radius);
194
+ outline: 2px solid transparent;
195
+ outline-offset: 2px;
196
+ }
197
+
198
+ .metric:focus-visible,
199
+ .metricOn {
200
+ outline-color: var(--text-tertiary);
201
+ }
@@ -0,0 +1,27 @@
1
+ "use client";
2
+ import { keepPreviousData, useQuery } from "@tanstack/react-query";
3
+ import {
4
+ adminEarningsApi
5
+ } from "@withpotter/lib/admin-earnings-api";
6
+ function useEarnings(range) {
7
+ return useQuery({
8
+ queryKey: ["platform-admin", "earnings", "summary", range],
9
+ queryFn: () => adminEarningsApi.summary(range),
10
+ retry: false,
11
+ staleTime: 6e4
12
+ });
13
+ }
14
+ function useEarningsTransactions(filters) {
15
+ return useQuery({
16
+ queryKey: ["platform-admin", "earnings", "transactions", filters],
17
+ queryFn: () => adminEarningsApi.transactions(filters),
18
+ retry: false,
19
+ staleTime: 3e4,
20
+ placeholderData: keepPreviousData
21
+ });
22
+ }
23
+ export {
24
+ useEarnings,
25
+ useEarningsTransactions
26
+ };
27
+ //# sourceMappingURL=use-earnings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/admin/revenue/earnings/use-earnings.ts"],"sourcesContent":["'use client';\n\nimport { keepPreviousData, useQuery } from '@tanstack/react-query';\nimport {\n adminEarningsApi,\n type EarningsFilters,\n} from '@withpotter/lib/admin-earnings-api';\n\n/** What Olive earns in the window, by source, day and merchant. A box without the route answers 404: not available here. */\nexport function useEarnings(range: { startDate?: string; endDate?: string }) {\n return useQuery({\n queryKey: ['platform-admin', 'earnings', 'summary', range],\n queryFn: () => adminEarningsApi.summary(range),\n retry: false,\n staleTime: 60_000,\n });\n}\n\n/** The fee transactions behind the earnings, a page at a time. */\nexport function useEarningsTransactions(filters: EarningsFilters) {\n return useQuery({\n queryKey: ['platform-admin', 'earnings', 'transactions', filters],\n queryFn: () => adminEarningsApi.transactions(filters),\n retry: false,\n staleTime: 30_000,\n placeholderData: keepPreviousData,\n });\n}\n"],"mappings":";AAEA,SAAS,kBAAkB,gBAAgB;AAC3C;AAAA,EACE;AAAA,OAEK;AAGA,SAAS,YAAY,OAAiD;AAC3E,SAAO,SAAS;AAAA,IACd,UAAU,CAAC,kBAAkB,YAAY,WAAW,KAAK;AAAA,IACzD,SAAS,MAAM,iBAAiB,QAAQ,KAAK;AAAA,IAC7C,OAAO;AAAA,IACP,WAAW;AAAA,EACb,CAAC;AACH;AAGO,SAAS,wBAAwB,SAA0B;AAChE,SAAO,SAAS;AAAA,IACd,UAAU,CAAC,kBAAkB,YAAY,gBAAgB,OAAO;AAAA,IAChE,SAAS,MAAM,iBAAiB,aAAa,OAAO;AAAA,IACpD,OAAO;AAAA,IACP,WAAW;AAAA,IACX,iBAAiB;AAAA,EACnB,CAAC;AACH;","names":[]}
@@ -2,6 +2,7 @@ import { jsx } from "react/jsx-runtime";
2
2
  import { AdmLoading, Panel } from "../_ui";
3
3
  import { FigureList } from "./figure-list";
4
4
  const integer = new Intl.NumberFormat("en-US");
5
+ const pct = (value) => value == null ? "\u2013" : `${value}%`;
5
6
  function HealthPanel({
6
7
  health,
7
8
  money,
@@ -21,12 +22,12 @@ function HealthPanel({
21
22
  {
22
23
  label: "Plan income kept",
23
24
  note: "Share of the plan income at the start that is still being paid",
24
- value: `${health.grossRevenueRetentionPct}%`
25
+ value: pct(health.grossRevenueRetentionPct)
25
26
  },
26
27
  {
27
28
  label: "Plan income lost",
28
29
  note: "Share of plan income lost to merchants who left",
29
- value: `${health.grossMrrChurnPct}%`,
30
+ value: pct(health.grossMrrChurnPct),
30
31
  tone: health.grossMrrChurnPct > 0 ? "danger" : "default"
31
32
  },
32
33
  {
@@ -41,13 +42,13 @@ function HealthPanel({
41
42
  {
42
43
  label: "Paying merchants lost",
43
44
  note: "Share of paying merchants who stopped paying",
44
- value: `${health.logoChurnPct}%`,
45
+ value: pct(health.logoChurnPct),
45
46
  tone: health.logoChurnPct > 0 ? "danger" : "default"
46
47
  },
47
48
  {
48
49
  label: "Reliance on the top 10",
49
50
  note: "Share of merchant sales that comes from the 10 biggest merchants",
50
- value: `${health.gmvConcentrationTop10Pct}%`,
51
+ value: pct(health.gmvConcentrationTop10Pct),
51
52
  tone: health.gmvConcentrationTop10Pct > 50 ? "danger" : "default"
52
53
  }
53
54
  ];
@@ -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=\"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":[]}
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/** A share, or a dash when there is no base to take it of (the api sends null then, not 0). */\nconst pct = (value: number | null | undefined) => (value == null ? '–' : `${value}%`);\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: pct(health.grossRevenueRetentionPct),\n },\n {\n label: 'Plan income lost',\n note: 'Share of plan income lost to merchants who left',\n value: pct(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: pct(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: pct(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":"AAsBQ;AArBR,SAAS,YAAY,aAAa;AAClC,SAAS,kBAA+B;AAExC,MAAM,UAAU,IAAI,KAAK,aAAa,OAAO;AAG7C,MAAM,MAAM,CAAC,UAAsC,SAAS,OAAO,WAAM,GAAG,KAAK;AAG1E,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,IAAI,OAAO,wBAAwB;AAAA,IAC5C;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,MAAM;AAAA,MACN,OAAO,IAAI,OAAO,gBAAgB;AAAA,MAClC,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,IAAI,OAAO,YAAY;AAAA,MAC9B,MAAM,OAAO,eAAe,IAAI,WAAW;AAAA,IAC7C;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,MAAM;AAAA,MACN,OAAO,IAAI,OAAO,wBAAwB;AAAA,MAC1C,MAAM,OAAO,2BAA2B,KAAK,WAAW;AAAA,IAC1D;AAAA,EACF;AAEA,SACE,oBAAC,SAAM,OAAM,+BAA8B,SACzC,8BAAC,cAAW,MAAY,GAC1B;AAEJ;","names":[]}
@@ -41,7 +41,7 @@ function RevenueKpis({
41
41
  label: "Plan income each month",
42
42
  value: tracked ? /* @__PURE__ */ jsx(CountUp, { value: recurring.mrr, format: money }) : notTracked,
43
43
  hint: `What merchants on a paid plan are expected to pay ${ADMIN_NAME} every month, in ${data.currency || "NGN"}. Free plans, trials and pauses are not counted.`,
44
- sub: tracked ? `${integer.format(recurring.payingMerchants ?? recurring.activeSubscriptions)} merchants paying` : noSource
44
+ sub: tracked ? `${integer.format(recurring.payingMerchants ?? recurring.activeSubscriptions)} ${(recurring.payingMerchants ?? recurring.activeSubscriptions) === 1 ? "merchant" : "merchants"} paying` : noSource
45
45
  }
46
46
  ) }),
47
47
  /* @__PURE__ */ jsx(Reveal, { index: 1, className: "adm-span-3", children: /* @__PURE__ */ jsx(
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/revenue/revenue-kpis.tsx"],"sourcesContent":["import { ADMIN_NAME } from '../_shell/admin-brand';\nimport type { ReactNode } from 'react';\nimport type { PlatformRevenue } from '@withpotter/lib/admin-api';\nimport { CountUp, Reveal, TONE } from '../_fx';\nimport { MoneyLines, StatCard, rowFor } from '../_ui';\n\nconst integer = new Intl.NumberFormat('en-US');\n\n/**\n * The headline figures, in two groups that are never added together: what\n * merchants sold (`merchant`), and what Olive itself earns from plans (`olive`).\n */\nexport function RevenueKpis({\n part,\n extra,\n data,\n money,\n money2,\n currency,\n focus,\n gmvSpark,\n}: {\n part: 'merchant' | 'olive';\n /** More cards for the merchant group, in the same row. */\n extra?: ReactNode;\n data: PlatformRevenue;\n /** Formats recurring revenue, in the currency the API names for it. */\n money: (value: number) => string;\n money2: (value: number) => string;\n /** The currency filter's choice; empty shows a line per currency. */\n currency: string;\n /** The one currency the share and sparkline follow, or null. */\n focus: string | null;\n gmvSpark: number[];\n}) {\n const { recurring } = data;\n // Without the plan payments connected the API sends 0s; say so rather than show a zero.\n const tracked = recurring.tracked !== false;\n const notTracked = 'Not set up yet';\n const noSource = 'The plan payments are not connected yet';\n const gmv = rowFor(data.gmvByCurrency, focus);\n const lifetimeShare =\n gmv && gmv.lifetime > 0\n ? `${Math.round((gmv.inRange / gmv.lifetime) * 100)}% of lifetime`\n : undefined;\n\n return (\n <section\n aria-label={part === 'olive' ? 'What Olive earns' : 'What merchants sold'}\n className=\"adm-grid\"\n >\n {part === 'olive' && (\n <>\n <Reveal index={0} className=\"adm-span-3\">\n <StatCard\n label=\"Plan income collected\"\n value={\n tracked && recurring.collected !== undefined ? (\n <CountUp value={recurring.collected} format={money2} />\n ) : (\n notTracked\n )\n }\n hint={`Plan payments that actually went through in this window, in ${data.currency || 'NGN'}. The monthly figure beside it is what is expected, this is what came in.`}\n sub={tracked ? 'In this window' : noSource}\n />\n </Reveal>\n <Reveal index={1} className=\"adm-span-3\">\n <StatCard\n label=\"Plan income each month\"\n value={\n tracked ? (\n <CountUp value={recurring.mrr} format={money} />\n ) : (\n notTracked\n )\n }\n hint={`What merchants on a paid plan are expected to pay ${ADMIN_NAME} every month, in ${data.currency || 'NGN'}. Free plans, trials and pauses are not counted.`}\n sub={\n tracked\n ? `${integer.format(recurring.payingMerchants ?? recurring.activeSubscriptions)} merchants paying`\n : noSource\n }\n />\n </Reveal>\n <Reveal index={1} className=\"adm-span-3\">\n <StatCard\n label=\"Plan income over a year\"\n value={\n tracked ? (\n <CountUp value={recurring.arr} format={money} />\n ) : (\n notTracked\n )\n }\n hint=\"What plan income adds up to over a year if nothing changes: the monthly figure times 12.\"\n sub={tracked ? 'Each month times 12' : noSource}\n />\n </Reveal>\n <Reveal index={2} className=\"adm-span-3\">\n <StatCard\n label=\"Average per paying merchant\"\n value={\n tracked ? (\n <CountUp value={recurring.arpa} format={money2} />\n ) : (\n notTracked\n )\n }\n hint=\"What one paying merchant pays in a month, on average.\"\n sub={tracked ? 'Each month' : noSource}\n />\n </Reveal>\n </>\n )}\n {part === 'merchant' && (\n <>\n <Reveal index={3} className=\"adm-span-3\">\n <StatCard\n label=\"Merchant sales\"\n value={\n <MoneyLines\n rows={data.gmvByCurrency}\n currency={currency}\n amount={(g) => g.inRange}\n empty=\"No sales yet\"\n />\n }\n href=\"/admin/tenants\"\n sub={lifetimeShare}\n hint={`Everything merchants sold through ${ADMIN_NAME}. This is the merchants' money, not ${ADMIN_NAME}'s own income. One line per currency; currencies are never added together. (GMV)`}\n spark={focus ? gmvSpark : undefined}\n sparkColor={TONE.money.fg}\n />\n </Reveal>\n {extra}\n {data.merchantWalletsByCurrency && (\n <Reveal index={4} className=\"adm-span-3\">\n <StatCard\n label=\"In merchants' wallets\"\n value={\n <MoneyLines\n rows={data.merchantWalletsByCurrency}\n currency={currency}\n amount={(r) => r.amount}\n decimals={2}\n empty=\"Nothing held\"\n />\n }\n hint=\"What merchants hold in their wallets right now: pending, available, held back and on its way to a bank, added together. A balance today, so it does not follow the dates above.\"\n sub=\"Right now\"\n />\n </Reveal>\n )}\n {data.merchantPayoutsByCurrency && (\n <Reveal index={5} className=\"adm-span-3\">\n <StatCard\n label=\"Merchants sent out\"\n value={\n <MoneyLines\n rows={data.merchantPayoutsByCurrency}\n currency={currency}\n amount={(r) => r.amount}\n decimals={2}\n empty=\"Nothing sent out\"\n />\n }\n hint=\"What merchants withdrew to their bank accounts in this window. Payouts the bank sent back are taken off.\"\n />\n </Reveal>\n )}\n </>\n )}\n </section>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/revenue/revenue-kpis.tsx"],"sourcesContent":["import { ADMIN_NAME } from '../_shell/admin-brand';\nimport type { ReactNode } from 'react';\nimport type { PlatformRevenue } from '@withpotter/lib/admin-api';\nimport { CountUp, Reveal, TONE } from '../_fx';\nimport { MoneyLines, StatCard, rowFor } from '../_ui';\n\nconst integer = new Intl.NumberFormat('en-US');\n\n/**\n * The headline figures, in two groups that are never added together: what\n * merchants sold (`merchant`), and what Olive itself earns from plans (`olive`).\n */\nexport function RevenueKpis({\n part,\n extra,\n data,\n money,\n money2,\n currency,\n focus,\n gmvSpark,\n}: {\n part: 'merchant' | 'olive';\n /** More cards for the merchant group, in the same row. */\n extra?: ReactNode;\n data: PlatformRevenue;\n /** Formats recurring revenue, in the currency the API names for it. */\n money: (value: number) => string;\n money2: (value: number) => string;\n /** The currency filter's choice; empty shows a line per currency. */\n currency: string;\n /** The one currency the share and sparkline follow, or null. */\n focus: string | null;\n gmvSpark: number[];\n}) {\n const { recurring } = data;\n // Without the plan payments connected the API sends 0s; say so rather than show a zero.\n const tracked = recurring.tracked !== false;\n const notTracked = 'Not set up yet';\n const noSource = 'The plan payments are not connected yet';\n const gmv = rowFor(data.gmvByCurrency, focus);\n const lifetimeShare =\n gmv && gmv.lifetime > 0\n ? `${Math.round((gmv.inRange / gmv.lifetime) * 100)}% of lifetime`\n : undefined;\n\n return (\n <section\n aria-label={part === 'olive' ? 'What Olive earns' : 'What merchants sold'}\n className=\"adm-grid\"\n >\n {part === 'olive' && (\n <>\n <Reveal index={0} className=\"adm-span-3\">\n <StatCard\n label=\"Plan income collected\"\n value={\n tracked && recurring.collected !== undefined ? (\n <CountUp value={recurring.collected} format={money2} />\n ) : (\n notTracked\n )\n }\n hint={`Plan payments that actually went through in this window, in ${data.currency || 'NGN'}. The monthly figure beside it is what is expected, this is what came in.`}\n sub={tracked ? 'In this window' : noSource}\n />\n </Reveal>\n <Reveal index={1} className=\"adm-span-3\">\n <StatCard\n label=\"Plan income each month\"\n value={\n tracked ? (\n <CountUp value={recurring.mrr} format={money} />\n ) : (\n notTracked\n )\n }\n hint={`What merchants on a paid plan are expected to pay ${ADMIN_NAME} every month, in ${data.currency || 'NGN'}. Free plans, trials and pauses are not counted.`}\n sub={\n tracked\n ? `${integer.format(recurring.payingMerchants ?? recurring.activeSubscriptions)} ${(recurring.payingMerchants ?? recurring.activeSubscriptions) === 1 ? 'merchant' : 'merchants'} paying`\n : noSource\n }\n />\n </Reveal>\n <Reveal index={1} className=\"adm-span-3\">\n <StatCard\n label=\"Plan income over a year\"\n value={\n tracked ? (\n <CountUp value={recurring.arr} format={money} />\n ) : (\n notTracked\n )\n }\n hint=\"What plan income adds up to over a year if nothing changes: the monthly figure times 12.\"\n sub={tracked ? 'Each month times 12' : noSource}\n />\n </Reveal>\n <Reveal index={2} className=\"adm-span-3\">\n <StatCard\n label=\"Average per paying merchant\"\n value={\n tracked ? (\n <CountUp value={recurring.arpa} format={money2} />\n ) : (\n notTracked\n )\n }\n hint=\"What one paying merchant pays in a month, on average.\"\n sub={tracked ? 'Each month' : noSource}\n />\n </Reveal>\n </>\n )}\n {part === 'merchant' && (\n <>\n <Reveal index={3} className=\"adm-span-3\">\n <StatCard\n label=\"Merchant sales\"\n value={\n <MoneyLines\n rows={data.gmvByCurrency}\n currency={currency}\n amount={(g) => g.inRange}\n empty=\"No sales yet\"\n />\n }\n href=\"/admin/tenants\"\n sub={lifetimeShare}\n hint={`Everything merchants sold through ${ADMIN_NAME}. This is the merchants' money, not ${ADMIN_NAME}'s own income. One line per currency; currencies are never added together. (GMV)`}\n spark={focus ? gmvSpark : undefined}\n sparkColor={TONE.money.fg}\n />\n </Reveal>\n {extra}\n {data.merchantWalletsByCurrency && (\n <Reveal index={4} className=\"adm-span-3\">\n <StatCard\n label=\"In merchants' wallets\"\n value={\n <MoneyLines\n rows={data.merchantWalletsByCurrency}\n currency={currency}\n amount={(r) => r.amount}\n decimals={2}\n empty=\"Nothing held\"\n />\n }\n hint=\"What merchants hold in their wallets right now: pending, available, held back and on its way to a bank, added together. A balance today, so it does not follow the dates above.\"\n sub=\"Right now\"\n />\n </Reveal>\n )}\n {data.merchantPayoutsByCurrency && (\n <Reveal index={5} className=\"adm-span-3\">\n <StatCard\n label=\"Merchants sent out\"\n value={\n <MoneyLines\n rows={data.merchantPayoutsByCurrency}\n currency={currency}\n amount={(r) => r.amount}\n decimals={2}\n empty=\"Nothing sent out\"\n />\n }\n hint=\"What merchants withdrew to their bank accounts in this window. Payouts the bank sent back are taken off.\"\n />\n </Reveal>\n )}\n </>\n )}\n </section>\n );\n}\n"],"mappings":"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","names":[]}
@@ -1,63 +1,41 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
- import { useMemo } from "react";
4
- import { TimeSeriesChart } from "@withpotter/components/charts";
5
- import {
6
- MoneyLines,
7
- PICK_CURRENCY,
8
- PageTabs,
9
- Panel,
10
- StatCard,
11
- moneyIn,
12
- rowFor,
13
- toTrend,
14
- useTabParam
15
- } from "../_ui";
16
- import { Reveal } from "../_fx";
17
- import { Tinted } from "../audit/tinted";
18
- import { ChannelPanel } from "./channel-panel";
19
- import { MerchantHeadline } from "./merchant-headline";
20
- import { MethodCards } from "./method-cards";
3
+ import { EarningsSummary } from "./earnings/earnings-summary";
4
+ import { EarningsTable } from "./earnings/earnings-table";
21
5
  import { HealthPanel } from "./health-panel";
22
6
  import { PlanMixPanel } from "./plan-mix-panel";
23
7
  import { planLines } from "./plan-lines";
24
8
  import { PlansPanel } from "./plans-panel";
25
- import { platformFeeRows } from "./platform-fees";
26
9
  import { RevenueKpis } from "./revenue-kpis";
27
- import { SubscriptionsPanel } from "./subscriptions-panel";
28
- import { TopTenantsPanel } from "./top-tenants-panel";
29
- import styles from "./revenue.module.css";
30
10
  import ledgerStyles from "./ledger.module.css";
31
- const VIEWS = [
32
- { id: "olive", label: "Money Olive earns" },
33
- { id: "customers", label: "How customers pay" },
34
- { id: "merchants", label: "Money merchants earn" },
35
- { id: "plans", label: "Plan subscriptions" }
36
- ];
37
- const VIEW_KEYS = VIEWS.map((v) => v.id);
38
11
  function RevenueOverview({
12
+ startDate,
13
+ endDate,
39
14
  data,
40
15
  health,
41
16
  money,
42
17
  money2,
43
18
  currency,
44
19
  focus,
45
- period,
46
20
  span,
47
21
  recurringCaption,
48
- channels,
49
- ledger,
50
22
  plans
51
23
  }) {
52
- const [view, setView] = useTabParam(VIEW_KEYS, "view");
53
- const gmv = rowFor(data.gmvByCurrency, focus);
54
- const gmvSeries = useMemo(() => toTrend(gmv?.series, "gmv"), [gmv]);
55
- const gmvCaption = focus ? `${period} \xB7 ${focus}` : period;
56
24
  const recurringTracked = data.recurring.tracked !== false;
57
- const platformFees = platformFeeRows(data.platformFeesEarnedByCurrency, currency);
58
- const olive = /* @__PURE__ */ jsxs(Fragment, { children: [
25
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
26
+ /* @__PURE__ */ jsx(
27
+ EarningsSummary,
28
+ {
29
+ startDate,
30
+ endDate,
31
+ currency,
32
+ recurring: data.recurring,
33
+ span
34
+ }
35
+ ),
36
+ /* @__PURE__ */ jsx("h2", { className: ledgerStyles.sectionTitle, children: "Plan income" }),
59
37
  /* @__PURE__ */ jsxs("p", { className: ledgerStyles.sectionNote, children: [
60
- "What merchants pay Olive: their plan payments, and the fees Olive keeps.",
38
+ "What merchants pay Olive for their plans, each month and over a year.",
61
39
  recurringTracked ? "" : " Plan income is not set up on this box yet."
62
40
  ] }),
63
41
  /* @__PURE__ */ jsx(
@@ -72,22 +50,6 @@ function RevenueOverview({
72
50
  gmvSpark: []
73
51
  }
74
52
  ),
75
- platformFees && /* @__PURE__ */ jsx("section", { "aria-label": "Platform fees", className: "adm-grid", children: /* @__PURE__ */ jsx(Reveal, { index: 0, className: "adm-span-3", children: /* @__PURE__ */ jsx(
76
- StatCard,
77
- {
78
- label: "Fees Olive keeps",
79
- value: /* @__PURE__ */ jsx(
80
- MoneyLines,
81
- {
82
- rows: platformFees,
83
- currency: "",
84
- amount: (r) => r.amount,
85
- decimals: 2
86
- }
87
- ),
88
- hint: "What Olive keeps from payouts and plan payments, after costs."
89
- }
90
- ) }) }),
91
53
  recurringTracked && /* @__PURE__ */ jsxs("div", { className: "adm-grid", children: [
92
54
  /* @__PURE__ */ jsx("div", { className: plans ? "adm-span-12" : "adm-span-7", children: /* @__PURE__ */ jsx(HealthPanel, { health, money, caption: recurringCaption }) }),
93
55
  /* @__PURE__ */ jsx("div", { className: plans ? "adm-span-12" : "adm-span-5", children: plans ? /* @__PURE__ */ jsx(
@@ -105,69 +67,9 @@ function RevenueOverview({
105
67
  caption: recurringCaption
106
68
  }
107
69
  ) })
108
- ] })
109
- ] });
110
- const customers = /* @__PURE__ */ jsxs(Fragment, { children: [
111
- /* @__PURE__ */ jsx("p", { className: ledgerStyles.sectionNote, children: "The ways of paying on the checkout, and how much came in through each." }),
112
- ledger && focus ? /* @__PURE__ */ jsx(
113
- MethodCards,
114
- {
115
- now: ledger.byChannel.current,
116
- before: ledger.byChannel.previous,
117
- currency: focus
118
- }
119
- ) : channels && channels.length > 0 ? /* @__PURE__ */ jsx("div", { className: "adm-grid", children: /* @__PURE__ */ jsx("div", { className: "adm-span-12", children: /* @__PURE__ */ jsx(ChannelPanel, { totals: channels, focus, caption: gmvCaption }) }) }) : /* @__PURE__ */ jsx("p", { className: styles.empty, children: focus ? "Nothing to show for this period." : PICK_CURRENCY })
120
- ] });
121
- const merchants = /* @__PURE__ */ jsxs(Fragment, { children: [
122
- /* @__PURE__ */ jsx("p", { className: ledgerStyles.sectionNote, children: "What customers paid to merchants. This is the merchants' money, not Olive's own income." }),
123
- /* @__PURE__ */ jsx(
124
- RevenueKpis,
125
- {
126
- part: "merchant",
127
- data,
128
- money,
129
- money2,
130
- currency,
131
- focus,
132
- gmvSpark: gmvSeries.map((p) => p.value),
133
- extra: ledger && focus ? /* @__PURE__ */ jsx(MerchantHeadline, { ledger, focus }) : null
134
- }
135
- ),
136
- /* @__PURE__ */ jsx("div", { className: "adm-grid", children: /* @__PURE__ */ jsx("div", { className: "adm-span-12", children: /* @__PURE__ */ jsx(
137
- Panel,
138
- {
139
- title: "Merchant sales over time",
140
- caption: gmvCaption,
141
- height: "calc(var(--adm-panel-h) + 32px)",
142
- children: focus ? /* @__PURE__ */ jsx("div", { className: styles.flatChart, children: /* @__PURE__ */ jsx(Tinted, { tone: "money", children: /* @__PURE__ */ jsx(
143
- TimeSeriesChart,
144
- {
145
- data: gmvSeries,
146
- compare: null,
147
- compareLabel: `Previous ${span}`,
148
- formatValue: moneyIn(focus),
149
- emptyMessage: `No paid ${focus} orders in this window yet`,
150
- height: 240
151
- }
152
- ) }) }) : /* @__PURE__ */ jsx("p", { className: styles.empty, children: PICK_CURRENCY })
153
- }
154
- ) }) }),
155
- /* @__PURE__ */ jsx("div", { className: "adm-grid", children: /* @__PURE__ */ jsx("div", { className: "adm-span-12", children: /* @__PURE__ */ jsx(
156
- TopTenantsPanel,
157
- {
158
- tenants: gmv?.topTenants ?? [],
159
- lifetime: gmv?.lifetime ?? 0,
160
- focus
161
- }
162
- ) }) })
163
- ] });
164
- const planSubscriptions = recurringTracked ? /* @__PURE__ */ jsx("div", { className: "adm-grid", children: /* @__PURE__ */ jsx("div", { className: "adm-span-12", children: /* @__PURE__ */ jsx(SubscriptionsPanel, { recurring: data.recurring, span }) }) }) : /* @__PURE__ */ jsx("p", { className: styles.empty, children: "Plan income is not set up on this box yet." });
165
- return /* @__PURE__ */ jsxs(Fragment, { children: [
166
- /* @__PURE__ */ jsx(PageTabs, { tabs: VIEWS, active: view, onChange: setView }),
167
- view === "olive" && olive,
168
- view === "customers" && customers,
169
- view === "merchants" && merchants,
170
- view === "plans" && planSubscriptions
70
+ ] }),
71
+ /* @__PURE__ */ jsx("h2", { className: ledgerStyles.sectionTitle, children: "Every earning" }),
72
+ /* @__PURE__ */ jsx(EarningsTable, { startDate, endDate })
171
73
  ] });
172
74
  }
173
75
  export {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/revenue/revenue-overview.tsx"],"sourcesContent":["'use client';\n\nimport { useMemo } from 'react';\nimport { TimeSeriesChart } from '@withpotter/components/charts';\nimport type {\n GatewayChannelTotal,\n MerchantPaymentsPage,\n PlatformCockpit,\n PlatformRevenue,\n} from '@withpotter/lib/admin-api';\nimport type { PlanList } from '@withpotter/lib/pricing-plans-types';\nimport {\n MoneyLines,\n PICK_CURRENCY,\n PageTabs,\n Panel,\n StatCard,\n moneyIn,\n rowFor,\n toTrend,\n useTabParam,\n} from '../_ui';\nimport { Reveal } from '../_fx';\nimport { Tinted } from '../audit/tinted';\nimport { ChannelPanel } from './channel-panel';\nimport { MerchantHeadline } from './merchant-headline';\nimport { MethodCards } from './method-cards';\nimport { HealthPanel } from './health-panel';\nimport { PlanMixPanel } from './plan-mix-panel';\nimport { planLines } from './plan-lines';\nimport { PlansPanel } from './plans-panel';\nimport { platformFeeRows } from './platform-fees';\nimport { RevenueKpis } from './revenue-kpis';\nimport { SubscriptionsPanel } from './subscriptions-panel';\nimport { TopTenantsPanel } from './top-tenants-panel';\nimport styles from './revenue.module.css';\nimport ledgerStyles from './ledger.module.css';\n\n/** The Overview's own tabs, in the order the money is read: Olive's, how customers pay, merchants', then plans. */\nconst VIEWS = [\n { id: 'olive', label: 'Money Olive earns' },\n { id: 'customers', label: 'How customers pay' },\n { id: 'merchants', label: 'Money merchants earn' },\n { id: 'plans', label: 'Plan subscriptions' },\n] as const;\n\nconst VIEW_KEYS = VIEWS.map((v) => v.id);\n\n/**\n * Overview tab, one view at a time: what Olive earns (headline figures, fees, revenue health and plan mix), how\n * customers pay, what merchants earn (the GMV trend and the leaderboard), and the plan subscriptions. Merchant GMV is\n * per currency: the chart and the leaderboard follow one currency (`focus`), never a mix.\n */\nexport function RevenueOverview({\n data,\n health,\n money,\n money2,\n currency,\n focus,\n period,\n span,\n recurringCaption,\n channels,\n ledger,\n plans,\n}: {\n data: PlatformRevenue;\n health: PlatformCockpit['health'] | undefined;\n /** Formats recurring revenue, in the currency the API names for it. */\n money: (value: number) => string;\n money2: (value: number) => string;\n /** The currency filter's choice; empty shows a line per currency. */\n currency: string;\n /** The one currency the chart and leaderboard show, or null. */\n focus: string | null;\n period: string;\n span: string;\n recurringCaption: string;\n /** How customers paid, per channel and currency. Absent when the box cannot say. */\n channels?: readonly GatewayChannelTotal[];\n /** The merchant payments view's totals, with the period before; absent on a box without it. */\n ledger?: MerchantPaymentsPage;\n /** Every plan created, from Pricing. Absent while it loads or when it cannot be read. */\n plans?: PlanList;\n}) {\n const [view, setView] = useTabParam(VIEW_KEYS, 'view');\n const gmv = rowFor(data.gmvByCurrency, focus);\n const gmvSeries = useMemo(() => toTrend(gmv?.series, 'gmv'), [gmv]);\n const gmvCaption = focus ? `${period} · ${focus}` : period;\n const recurringTracked = data.recurring.tracked !== false;\n // Hidden until the api sends the field; never an invented zero.\n const platformFees = platformFeeRows(data.platformFeesEarnedByCurrency, currency);\n\n const olive = (\n <>\n <p className={ledgerStyles.sectionNote}>\n What merchants pay Olive: their plan payments, and the fees Olive keeps.\n {recurringTracked ? '' : ' Plan income is not set up on this box yet.'}\n </p>\n <RevenueKpis\n part=\"olive\"\n data={data}\n money={money}\n money2={money2}\n currency={currency}\n focus={focus}\n gmvSpark={[]}\n />\n\n {platformFees && (\n <section aria-label=\"Platform fees\" className=\"adm-grid\">\n <Reveal index={0} className=\"adm-span-3\">\n <StatCard\n label=\"Fees Olive keeps\"\n value={\n <MoneyLines\n rows={platformFees}\n currency=\"\"\n amount={(r) => r.amount}\n decimals={2}\n />\n }\n hint=\"What Olive keeps from payouts and plan payments, after costs.\"\n />\n </Reveal>\n </section>\n )}\n\n {recurringTracked && (\n <div className=\"adm-grid\">\n <div className={plans ? 'adm-span-12' : 'adm-span-7'}>\n <HealthPanel health={health} money={money} caption={recurringCaption} />\n </div>\n {/* The Plans table has five columns, so it gets a full row; the older plan mix keeps its narrow slot. */}\n <div className={plans ? 'adm-span-12' : 'adm-span-5'}>\n {plans ? (\n <PlansPanel\n lines={planLines(plans.plans, plans.currencies, data.recurring.planBreakdown)}\n money={money}\n caption={recurringCaption}\n />\n ) : (\n <PlanMixPanel\n planBreakdown={data.recurring.planBreakdown}\n money={money}\n caption={recurringCaption}\n />\n )}\n </div>\n </div>\n )}\n </>\n );\n\n const customers = (\n <>\n <p className={ledgerStyles.sectionNote}>\n The ways of paying on the checkout, and how much came in through each.\n </p>\n {ledger && focus ? (\n <MethodCards\n now={ledger.byChannel.current}\n before={ledger.byChannel.previous}\n currency={focus}\n />\n ) : channels && channels.length > 0 ? (\n <div className=\"adm-grid\">\n <div className=\"adm-span-12\">\n <ChannelPanel totals={channels} focus={focus} caption={gmvCaption} />\n </div>\n </div>\n ) : (\n <p className={styles.empty}>{focus ? 'Nothing to show for this period.' : PICK_CURRENCY}</p>\n )}\n </>\n );\n\n const merchants = (\n <>\n <p className={ledgerStyles.sectionNote}>\n What customers paid to merchants. This is the merchants' money, not Olive's own income.\n </p>\n <RevenueKpis\n part=\"merchant\"\n data={data}\n money={money}\n money2={money2}\n currency={currency}\n focus={focus}\n gmvSpark={gmvSeries.map((p) => p.value)}\n extra={ledger && focus ? <MerchantHeadline ledger={ledger} focus={focus} /> : null}\n />\n\n <div className=\"adm-grid\">\n <div className=\"adm-span-12\">\n <Panel\n title=\"Merchant sales over time\"\n caption={gmvCaption}\n height=\"calc(var(--adm-panel-h) + 32px)\"\n >\n {focus ? (\n <div className={styles.flatChart}>\n <Tinted tone=\"money\">\n <TimeSeriesChart\n data={gmvSeries}\n compare={null}\n compareLabel={`Previous ${span}`}\n formatValue={moneyIn(focus)}\n emptyMessage={`No paid ${focus} orders in this window yet`}\n height={240}\n />\n </Tinted>\n </div>\n ) : (\n <p className={styles.empty}>{PICK_CURRENCY}</p>\n )}\n </Panel>\n </div>\n </div>\n\n <div className=\"adm-grid\">\n <div className=\"adm-span-12\">\n <TopTenantsPanel\n tenants={gmv?.topTenants ?? []}\n lifetime={gmv?.lifetime ?? 0}\n focus={focus}\n />\n </div>\n </div>\n </>\n );\n\n const planSubscriptions = recurringTracked ? (\n <div className=\"adm-grid\">\n <div className=\"adm-span-12\">\n <SubscriptionsPanel recurring={data.recurring} span={span} />\n </div>\n </div>\n ) : (\n <p className={styles.empty}>Plan income is not set up on this box yet.</p>\n );\n\n return (\n <>\n <PageTabs tabs={VIEWS} active={view} onChange={setView} />\n {view === 'olive' && olive}\n {view === 'customers' && customers}\n {view === 'merchants' && merchants}\n {view === 'plans' && planSubscriptions}\n </>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/revenue/revenue-overview.tsx"],"sourcesContent":["'use client';\n\nimport type { PlatformCockpit, PlatformRevenue } from '@withpotter/lib/admin-api';\nimport type { PlanList } from '@withpotter/lib/pricing-plans-types';\nimport { EarningsSummary } from './earnings/earnings-summary';\nimport { EarningsTable } from './earnings/earnings-table';\nimport { HealthPanel } from './health-panel';\nimport { PlanMixPanel } from './plan-mix-panel';\nimport { planLines } from './plan-lines';\nimport { PlansPanel } from './plans-panel';\nimport { RevenueKpis } from './revenue-kpis';\nimport ledgerStyles from './ledger.module.css';\n\n/**\n * Money Olive earns: one page, no sub-tabs, read top to bottom. What Olive made and kept (cards, pies, graphs, the\n * merchants it came from), then Olive's own plan income, then every earning in a table. It is Olive's money only: what\n * customers pay merchants lives on the Merchant payments tab, and the plan subscriptions on the Subscriptions tab.\n */\nexport function RevenueOverview({\n startDate,\n endDate,\n data,\n health,\n money,\n money2,\n currency,\n focus,\n span,\n recurringCaption,\n plans,\n}: {\n /** The window, for the earnings views, which read their own data. */\n startDate?: string;\n endDate?: string;\n data: PlatformRevenue;\n health: PlatformCockpit['health'] | undefined;\n /** Formats recurring revenue, in the currency the API names for it. */\n money: (value: number) => string;\n money2: (value: number) => string;\n /** The currency filter's choice; empty follows the billing currency. */\n currency: string;\n /** The one currency the plan figures show, or null. */\n focus: string | null;\n span: string;\n recurringCaption: string;\n /** Every plan created, from Pricing. Absent while it loads or when it cannot be read. */\n plans?: PlanList;\n}) {\n const recurringTracked = data.recurring.tracked !== false;\n\n return (\n <>\n <EarningsSummary\n startDate={startDate}\n endDate={endDate}\n currency={currency}\n recurring={data.recurring}\n span={span}\n />\n\n <h2 className={ledgerStyles.sectionTitle}>Plan income</h2>\n <p className={ledgerStyles.sectionNote}>\n What merchants pay Olive for their plans, each month and over a year.\n {recurringTracked ? '' : ' Plan income is not set up on this box yet.'}\n </p>\n <RevenueKpis\n part=\"olive\"\n data={data}\n money={money}\n money2={money2}\n currency={currency}\n focus={focus}\n gmvSpark={[]}\n />\n\n {recurringTracked && (\n <div className=\"adm-grid\">\n <div className={plans ? 'adm-span-12' : 'adm-span-7'}>\n <HealthPanel health={health} money={money} caption={recurringCaption} />\n </div>\n {/* The Plans table has five columns, so it gets a full row; the older plan mix keeps its narrow slot. */}\n <div className={plans ? 'adm-span-12' : 'adm-span-5'}>\n {plans ? (\n <PlansPanel\n lines={planLines(plans.plans, plans.currencies, data.recurring.planBreakdown)}\n money={money}\n caption={recurringCaption}\n />\n ) : (\n <PlanMixPanel\n planBreakdown={data.recurring.planBreakdown}\n money={money}\n caption={recurringCaption}\n />\n )}\n </div>\n </div>\n )}\n\n <h2 className={ledgerStyles.sectionTitle}>Every earning</h2>\n <EarningsTable startDate={startDate} endDate={endDate} />\n </>\n );\n}\n"],"mappings":";AAmDI,mBACE,KASA,YAVF;AA/CJ,SAAS,uBAAuB;AAChC,SAAS,qBAAqB;AAC9B,SAAS,mBAAmB;AAC5B,SAAS,oBAAoB;AAC7B,SAAS,iBAAiB;AAC1B,SAAS,kBAAkB;AAC3B,SAAS,mBAAmB;AAC5B,OAAO,kBAAkB;AAOlB,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAiBG;AACD,QAAM,mBAAmB,KAAK,UAAU,YAAY;AAEpD,SACE,iCACE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA,WAAW,KAAK;AAAA,QAChB;AAAA;AAAA,IACF;AAAA,IAEA,oBAAC,QAAG,WAAW,aAAa,cAAc,yBAAW;AAAA,IACrD,qBAAC,OAAE,WAAW,aAAa,aAAa;AAAA;AAAA,MAErC,mBAAmB,KAAK;AAAA,OAC3B;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,UAAU,CAAC;AAAA;AAAA,IACb;AAAA,IAEC,oBACC,qBAAC,SAAI,WAAU,YACb;AAAA,0BAAC,SAAI,WAAW,QAAQ,gBAAgB,cACtC,8BAAC,eAAY,QAAgB,OAAc,SAAS,kBAAkB,GACxE;AAAA,MAEA,oBAAC,SAAI,WAAW,QAAQ,gBAAgB,cACrC,kBACC;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,UAAU,MAAM,OAAO,MAAM,YAAY,KAAK,UAAU,aAAa;AAAA,UAC5E;AAAA,UACA,SAAS;AAAA;AAAA,MACX,IAEA;AAAA,QAAC;AAAA;AAAA,UACC,eAAe,KAAK,UAAU;AAAA,UAC9B;AAAA,UACA,SAAS;AAAA;AAAA,MACX,GAEJ;AAAA,OACF;AAAA,IAGF,oBAAC,QAAG,WAAW,aAAa,cAAc,2BAAa;AAAA,IACvD,oBAAC,iBAAc,WAAsB,SAAkB;AAAA,KACzD;AAEJ;","names":[]}
@@ -3,7 +3,6 @@ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useMemo } from "react";
4
4
  import { ADMIN_NAME } from "../_shell/admin-brand";
5
5
  import {
6
- useGatewayChannels,
7
6
  useMerchantPayments,
8
7
  usePlatformCockpit,
9
8
  usePlatformRevenue
@@ -31,7 +30,7 @@ import { RevenueRetention } from "./revenue-retention";
31
30
  import { SubscriptionsView } from "./subscriptions-view";
32
31
  import { SummaryDownload } from "./summary-download";
33
32
  const TABS = [
34
- { id: "overview", label: "Overview" },
33
+ { id: "overview", label: "Money Olive earns" },
35
34
  { id: "merchants", label: "Merchant payments" },
36
35
  { id: "subscriptions", label: "Subscriptions" },
37
36
  { id: "retention", label: "Retention" }
@@ -60,10 +59,6 @@ function RevenueWorkspace() {
60
59
  startDate: range.startDate,
61
60
  endDate: range.endDate
62
61
  });
63
- const channels = useGatewayChannels({
64
- startDate: range.startDate,
65
- endDate: range.endDate
66
- });
67
62
  const ledger = useMerchantPayments({
68
63
  startDate: range.startDate,
69
64
  endDate: range.endDate,
@@ -122,17 +117,16 @@ function RevenueWorkspace() {
122
117
  body = /* @__PURE__ */ jsx(
123
118
  RevenueOverview,
124
119
  {
120
+ startDate: range.startDate,
121
+ endDate: range.endDate,
125
122
  data,
126
123
  health: cockpit.data?.health,
127
124
  money,
128
125
  money2,
129
126
  currency,
130
127
  focus,
131
- period,
132
128
  span,
133
129
  recurringCaption: `${period} \xB7 ${recurringCurrency}`,
134
- channels: channels.data?.byChannel,
135
- ledger: ledger.data,
136
130
  plans: plans.data
137
131
  }
138
132
  );
@@ -142,7 +136,7 @@ function RevenueWorkspace() {
142
136
  PageHeader,
143
137
  {
144
138
  title: "Revenue",
145
- description: `All the money moving through ${ADMIN_NAME}: what customers pay merchants, and what merchants pay ${ADMIN_NAME} for their plan.`,
139
+ description: `How money flows through ${ADMIN_NAME}: what it earns, where it comes from, what it costs and what is left.`,
146
140
  actions: /* @__PURE__ */ jsxs("div", { className: "adm-toolbar", children: [
147
141
  tab === "overview" ? /* @__PURE__ */ jsx(
148
142
  SummaryDownload,