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

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.
@@ -9,22 +9,26 @@ function pctChange(now, before) {
9
9
  return Number(((a - b) / Math.abs(b) * 100).toFixed(1));
10
10
  }
11
11
  const METHOD_ORDER = ["card", "ussd", "transfer", "opay"];
12
- function methodCards(now, before, currency) {
12
+ function methodCards(now, before, currency, options = {}) {
13
13
  const pick = (rows) => (rows ?? []).filter((r) => r.currency === currency);
14
14
  const current = pick(now);
15
15
  const total = current.reduce((sum, r) => sum + BigInt(r.amountMinor), BigInt(0));
16
- const labels = { card: "Card", ussd: "USSD", transfer: "Bank transfer", opay: "Opay" };
17
- const keys = [
18
- ...METHOD_ORDER,
19
- ...current.map((r) => r.channel.key).filter((k) => !METHOD_ORDER.includes(k))
20
- ];
16
+ const labels = {
17
+ card: "Card",
18
+ ussd: "USSD",
19
+ transfer: "Bank transfer",
20
+ opay: "Opay",
21
+ ...options.labels
22
+ };
23
+ const order = options.order ?? METHOD_ORDER;
24
+ const keys = [...order, ...current.map((r) => r.channel.key).filter((k) => !order.includes(k))];
21
25
  return [...new Set(keys)].map((key) => {
22
26
  const mine = current.find((r) => r.channel.key === key);
23
27
  const prior = pick(before).find((r) => r.channel.key === key);
24
28
  const amount = BigInt(mine?.amountMinor ?? "0");
25
29
  return {
26
30
  key,
27
- label: mine?.channel.label ?? labels[key] ?? key,
31
+ label: options.labels?.[key] ?? mine?.channel.label ?? labels[key] ?? key,
28
32
  payments: mine?.count ?? 0,
29
33
  amountMinor: amount.toString(),
30
34
  share: total > BigInt(0) ? Number((Number(amount) / Number(total) * 100).toFixed(1)) : 0,
@@ -60,8 +64,8 @@ const PURPOSE_WORDS = {
60
64
  mandate_setup: "Card saved"
61
65
  };
62
66
  const SUBSCRIPTION_METHOD_WORDS = {
63
- gateway: "Card or bank",
64
- wallet: "Wallet",
67
+ gateway: "Olive payment gateway",
68
+ wallet: "Box wallet",
65
69
  manual: "Recorded by hand"
66
70
  };
67
71
  const word = (words, key) => words[key] ?? key.replace(/_/g, " ");
@@ -75,6 +79,14 @@ function plainAmount(minor) {
75
79
  const digits = text.replace("-", "").padStart(3, "0");
76
80
  return `${negative ? "-" : ""}${digits.slice(0, -2)}.${digits.slice(-2)}`;
77
81
  }
82
+ function planPaymentTotals(byMethod, currency) {
83
+ return byMethod.map((row) => ({
84
+ channel: { key: row.method, label: word(SUBSCRIPTION_METHOD_WORDS, row.method) },
85
+ currency,
86
+ count: row.payments,
87
+ amountMinor: String(Math.round(row.amount * 100))
88
+ }));
89
+ }
78
90
  export {
79
91
  METHOD_ORDER,
80
92
  PURPOSE_WORDS,
@@ -86,6 +98,7 @@ export {
86
98
  minorToMoney,
87
99
  pctChange,
88
100
  plainAmount,
101
+ planPaymentTotals,
89
102
  word
90
103
  };
91
104
  //# sourceMappingURL=ledger-model.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/revenue/ledger-model.ts"],"sourcesContent":["import type { GatewayChannelTotal } from '@withpotter/lib/admin-api';\nimport { formatMoney } from '../_ui/money-view';\n\n/** A kobo amount (a string, so no float is involved on the server) shown as naira. */\nexport function minorToMoney(minor: string | number, currency: string): string {\n return formatMoney(Number(minor) / 100, currency, 2);\n}\n\n/** Percent change from before to now; null when there was nothing before to compare with. */\nexport function pctChange(\n now: string | number | null | undefined,\n before: string | number | null | undefined,\n): number | null {\n const a = Number(now ?? 0);\n const b = Number(before ?? 0);\n if (b === 0) return null;\n\n return Number((((a - b) / Math.abs(b)) * 100).toFixed(1));\n}\n\n/** The four ways a customer can pay at the checkout, in the order the checkout shows them. */\nexport const METHOD_ORDER = ['card', 'ussd', 'transfer', 'opay'] as const;\n\nexport interface MethodCardRow {\n key: string;\n label: string;\n payments: number;\n amountMinor: string;\n /** Share of all money received in this currency, in percent. */\n share: number;\n /** Change in amount since the period before; null when there is no comparison. */\n change: number | null;\n}\n\n/**\n * One card per way of paying, for one currency. The four checkout methods are always there, with zero when nobody\n * used one, so a quiet method is visible rather than missing. Any other way found in the data follows.\n */\nexport function methodCards(\n now: readonly GatewayChannelTotal[],\n before: readonly GatewayChannelTotal[] | null,\n currency: string,\n): MethodCardRow[] {\n const pick = (rows: readonly GatewayChannelTotal[] | null) => (rows ?? []).filter((r) => r.currency === currency);\n const current = pick(now);\n const total = current.reduce((sum, r) => sum + BigInt(r.amountMinor), BigInt(0));\n const labels: Record<string, string> = { card: 'Card', ussd: 'USSD', transfer: 'Bank transfer', opay: 'Opay' };\n\n const keys = [\n ...METHOD_ORDER,\n ...current.map((r) => r.channel.key).filter((k) => !(METHOD_ORDER as readonly string[]).includes(k)),\n ];\n\n return [...new Set(keys)].map((key) => {\n const mine = current.find((r) => r.channel.key === key);\n const prior = pick(before).find((r) => r.channel.key === key);\n const amount = BigInt(mine?.amountMinor ?? '0');\n\n return {\n key,\n label: mine?.channel.label ?? labels[key] ?? key,\n payments: mine?.count ?? 0,\n amountMinor: amount.toString(),\n share: total > BigInt(0) ? Number(((Number(amount) / Number(total)) * 100).toFixed(1)) : 0,\n change: before ? pctChange(amount.toString(), prior?.amountMinor ?? '0') : null,\n };\n });\n}\n\n/** Words an accountant would use, not the database's. */\nexport const STATUS_WORDS: Record<string, string> = {\n success: 'Paid',\n pending: 'Waiting',\n failed: 'Failed',\n refunded: 'Refunded',\n cancelled: 'Cancelled',\n abandoned: 'Abandoned',\n succeeded: 'Paid',\n};\n\nexport const TYPE_WORDS: Record<string, string> = {\n order: 'Order',\n invoice: 'Invoice',\n booking: 'Booking',\n membership: 'Membership',\n subscription: 'Subscription',\n deposit: 'Deposit',\n tip: 'Tip',\n refund: 'Refund',\n payout: 'Payout',\n};\n\nexport const PURPOSE_WORDS: Record<string, string> = {\n select_plan: 'First payment',\n upgrade: 'Upgrade',\n renewal: 'Renewal',\n reactivation: 'Came back',\n mandate_setup: 'Card saved',\n};\n\nexport const SUBSCRIPTION_METHOD_WORDS: Record<string, string> = {\n gateway: 'Card or bank',\n wallet: 'Wallet',\n manual: 'Recorded by hand',\n};\n\nexport const word = (words: Record<string, string>, key: string): string => words[key] ?? key.replace(/_/g, ' ');\n\n/** The dates in a download's file name, so a file says what it holds. */\nexport function downloadName(prefix: string, startDate?: string, endDate?: string): string {\n const day = (iso?: string) => (iso ? iso.slice(0, 10) : 'all');\n\n return `${prefix}_${day(startDate)}_${day(endDate)}.csv`;\n}\n\n/** Kobo as plain text a spreadsheet reads (\"1234.50\"), with no currency sign or commas. */\nexport function plainAmount(minor: string | number): string {\n const text = String(minor);\n const negative = text.startsWith('-');\n const digits = text.replace('-', '').padStart(3, '0');\n\n return `${negative ? '-' : ''}${digits.slice(0, -2)}.${digits.slice(-2)}`;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/revenue/ledger-model.ts"],"sourcesContent":["import type { GatewayChannelTotal } from '@withpotter/lib/admin-api';\nimport { formatMoney } from '../_ui/money-view';\n\n/** A kobo amount (a string, so no float is involved on the server) shown as naira. */\nexport function minorToMoney(minor: string | number, currency: string): string {\n return formatMoney(Number(minor) / 100, currency, 2);\n}\n\n/** Percent change from before to now; null when there was nothing before to compare with. */\nexport function pctChange(\n now: string | number | null | undefined,\n before: string | number | null | undefined,\n): number | null {\n const a = Number(now ?? 0);\n const b = Number(before ?? 0);\n if (b === 0) return null;\n\n return Number((((a - b) / Math.abs(b)) * 100).toFixed(1));\n}\n\n/** The four ways a customer can pay at the checkout, in the order the checkout shows them. */\nexport const METHOD_ORDER = ['card', 'ussd', 'transfer', 'opay'] as const;\n\nexport interface MethodCardRow {\n key: string;\n label: string;\n payments: number;\n amountMinor: string;\n /** Share of all money received in this currency, in percent. */\n share: number;\n /** Change in amount since the period before; null when there is no comparison. */\n change: number | null;\n}\n\n/**\n * One card per way of paying, for one currency. The four checkout methods are always there, with zero when nobody\n * used one, so a quiet method is visible rather than missing. Any other way found in the data follows.\n */\nexport function methodCards(\n now: readonly GatewayChannelTotal[],\n before: readonly GatewayChannelTotal[] | null,\n currency: string,\n /** Other ways of paying and their names, for money that does not come through checkout (plan payments). */\n options: { order?: readonly string[]; labels?: Record<string, string> } = {},\n): MethodCardRow[] {\n const pick = (rows: readonly GatewayChannelTotal[] | null) => (rows ?? []).filter((r) => r.currency === currency);\n const current = pick(now);\n const total = current.reduce((sum, r) => sum + BigInt(r.amountMinor), BigInt(0));\n const labels: Record<string, string> = {\n card: 'Card',\n ussd: 'USSD',\n transfer: 'Bank transfer',\n opay: 'Opay',\n ...options.labels,\n };\n const order: readonly string[] = options.order ?? METHOD_ORDER;\n\n const keys = [...order, ...current.map((r) => r.channel.key).filter((k) => !order.includes(k))];\n\n return [...new Set(keys)].map((key) => {\n const mine = current.find((r) => r.channel.key === key);\n const prior = pick(before).find((r) => r.channel.key === key);\n const amount = BigInt(mine?.amountMinor ?? '0');\n\n return {\n key,\n label: options.labels?.[key] ?? mine?.channel.label ?? labels[key] ?? key,\n payments: mine?.count ?? 0,\n amountMinor: amount.toString(),\n share: total > BigInt(0) ? Number(((Number(amount) / Number(total)) * 100).toFixed(1)) : 0,\n change: before ? pctChange(amount.toString(), prior?.amountMinor ?? '0') : null,\n };\n });\n}\n\n/** Words an accountant would use, not the database's. */\nexport const STATUS_WORDS: Record<string, string> = {\n success: 'Paid',\n pending: 'Waiting',\n failed: 'Failed',\n refunded: 'Refunded',\n cancelled: 'Cancelled',\n abandoned: 'Abandoned',\n succeeded: 'Paid',\n};\n\nexport const TYPE_WORDS: Record<string, string> = {\n order: 'Order',\n invoice: 'Invoice',\n booking: 'Booking',\n membership: 'Membership',\n subscription: 'Subscription',\n deposit: 'Deposit',\n tip: 'Tip',\n refund: 'Refund',\n payout: 'Payout',\n};\n\nexport const PURPOSE_WORDS: Record<string, string> = {\n select_plan: 'First payment',\n upgrade: 'Upgrade',\n renewal: 'Renewal',\n reactivation: 'Came back',\n mandate_setup: 'Card saved',\n};\n\nexport const SUBSCRIPTION_METHOD_WORDS: Record<string, string> = {\n gateway: 'Olive payment gateway',\n wallet: 'Box wallet',\n manual: 'Recorded by hand',\n};\n\nexport const word = (words: Record<string, string>, key: string): string => words[key] ?? key.replace(/_/g, ' ');\n\n/** The dates in a download's file name, so a file says what it holds. */\nexport function downloadName(prefix: string, startDate?: string, endDate?: string): string {\n const day = (iso?: string) => (iso ? iso.slice(0, 10) : 'all');\n\n return `${prefix}_${day(startDate)}_${day(endDate)}.csv`;\n}\n\n/** Kobo as plain text a spreadsheet reads (\"1234.50\"), with no currency sign or commas. */\nexport function plainAmount(minor: string | number): string {\n const text = String(minor);\n const negative = text.startsWith('-');\n const digits = text.replace('-', '').padStart(3, '0');\n\n return `${negative ? '-' : ''}${digits.slice(0, -2)}.${digits.slice(-2)}`;\n}\n\n/**\n * The plan payments by way of paying, as the channel totals the method cards read. The api sends naira with kobo\n * (whole kobo after the point); they are turned back into whole kobo here, never rounded to naira.\n */\nexport function planPaymentTotals(\n byMethod: ReadonlyArray<{ method: string; payments: number; amount: number }>,\n currency: string,\n): GatewayChannelTotal[] {\n return byMethod.map((row) => ({\n channel: { key: row.method, label: word(SUBSCRIPTION_METHOD_WORDS, row.method) },\n currency,\n count: row.payments,\n amountMinor: String(Math.round(row.amount * 100)),\n }));\n}\n"],"mappings":"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","names":[]}
@@ -6,10 +6,18 @@ const integer = new Intl.NumberFormat("en-US");
6
6
  function MethodCards({
7
7
  now,
8
8
  before,
9
- currency
9
+ currency,
10
+ order,
11
+ labels,
12
+ title = "How customers paid",
13
+ received = "of the money received",
14
+ describe = (label) => `Money customers paid by ${label.toLowerCase()}, before fees.`,
15
+ hide
10
16
  }) {
11
- const cards = methodCards(now, before, currency);
12
- return /* @__PURE__ */ jsx("section", { "aria-label": "How customers paid", className: "adm-grid", children: cards.map((card, index) => /* @__PURE__ */ jsx(Reveal, { index, className: "adm-span-3", children: /* @__PURE__ */ jsx(
17
+ const cards = methodCards(now, before, currency, { order, labels }).filter(
18
+ (card) => !(hide?.includes(card.key) && card.payments === 0)
19
+ );
20
+ return /* @__PURE__ */ jsx("section", { "aria-label": title, className: "adm-grid", children: cards.map((card, index) => /* @__PURE__ */ jsx(Reveal, { index, className: "adm-span-3", children: /* @__PURE__ */ jsx(
13
21
  StatCard,
14
22
  {
15
23
  label: card.label,
@@ -20,8 +28,8 @@ function MethodCards({
20
28
  format: (v) => minorToMoney(Math.round(v * 100), currency)
21
29
  }
22
30
  ),
23
- sub: card.payments === 0 ? "No payments this way yet" : `${integer.format(card.payments)} ${card.payments === 1 ? "payment" : "payments"} \xB7 ${card.share}% of the money received`,
24
- hint: `Money customers paid by ${card.label.toLowerCase()}, before fees.`,
31
+ sub: card.payments === 0 ? "No payments this way yet" : `${integer.format(card.payments)} ${card.payments === 1 ? "payment" : "payments"} \xB7 ${card.share}% ${received}`,
32
+ hint: describe(card.label, card.key),
25
33
  delta: card.change === null ? void 0 : { pct: card.change, caption: "vs the period before" }
26
34
  }
27
35
  ) }, card.key)) });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/revenue/method-cards.tsx"],"sourcesContent":["import type { GatewayChannelTotal } from '@withpotter/lib/admin-api';\nimport { CountUp, Reveal } from '../_fx';\nimport { StatCard } from '../_ui';\nimport { methodCards, minorToMoney } from './ledger-model';\n\nconst integer = new Intl.NumberFormat('en-US');\n\n/**\n * One card for each way customers pay (Card, USSD, Bank transfer, Opay): how\n * much came in that way, how many payments, its share of all the money\n * received, and how it moved since the period before. A quiet method shows\n * zero, so it is seen rather than missing.\n */\nexport function MethodCards({\n now,\n before,\n currency,\n}: {\n now: readonly GatewayChannelTotal[];\n before: readonly GatewayChannelTotal[] | null;\n currency: string;\n}) {\n const cards = methodCards(now, before, currency);\n\n return (\n <section aria-label=\"How customers paid\" className=\"adm-grid\">\n {cards.map((card, index) => (\n <Reveal key={card.key} index={index} className=\"adm-span-3\">\n <StatCard\n label={card.label}\n value={\n <CountUp\n value={Number(card.amountMinor) / 100}\n format={(v) => minorToMoney(Math.round(v * 100), currency)}\n />\n }\n sub={\n card.payments === 0\n ? 'No payments this way yet'\n : `${integer.format(card.payments)} ${card.payments === 1 ? 'payment' : 'payments'} · ${card.share}% of the money received`\n }\n hint={`Money customers paid by ${card.label.toLowerCase()}, before fees.`}\n delta={\n card.change === null\n ? undefined\n : { pct: card.change, caption: 'vs the period before' }\n }\n />\n </Reveal>\n ))}\n </section>\n );\n}\n"],"mappings":"AA+Bc;AA9Bd,SAAS,SAAS,cAAc;AAChC,SAAS,gBAAgB;AACzB,SAAS,aAAa,oBAAoB;AAE1C,MAAM,UAAU,IAAI,KAAK,aAAa,OAAO;AAQtC,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,QAAM,QAAQ,YAAY,KAAK,QAAQ,QAAQ;AAE/C,SACE,oBAAC,aAAQ,cAAW,sBAAqB,WAAU,YAChD,gBAAM,IAAI,CAAC,MAAM,UAChB,oBAAC,UAAsB,OAAc,WAAU,cAC7C;AAAA,IAAC;AAAA;AAAA,MACC,OAAO,KAAK;AAAA,MACZ,OACE;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,OAAO,KAAK,WAAW,IAAI;AAAA,UAClC,QAAQ,CAAC,MAAM,aAAa,KAAK,MAAM,IAAI,GAAG,GAAG,QAAQ;AAAA;AAAA,MAC3D;AAAA,MAEF,KACE,KAAK,aAAa,IACd,6BACA,GAAG,QAAQ,OAAO,KAAK,QAAQ,CAAC,IAAI,KAAK,aAAa,IAAI,YAAY,UAAU,SAAM,KAAK,KAAK;AAAA,MAEtG,MAAM,2BAA2B,KAAK,MAAM,YAAY,CAAC;AAAA,MACzD,OACE,KAAK,WAAW,OACZ,SACA,EAAE,KAAK,KAAK,QAAQ,SAAS,uBAAuB;AAAA;AAAA,EAE5D,KApBW,KAAK,GAqBlB,CACD,GACH;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/revenue/method-cards.tsx"],"sourcesContent":["import type { GatewayChannelTotal } from '@withpotter/lib/admin-api';\nimport { CountUp, Reveal } from '../_fx';\nimport { StatCard } from '../_ui';\nimport { methodCards, minorToMoney } from './ledger-model';\n\nconst integer = new Intl.NumberFormat('en-US');\n\n/**\n * One card for each way customers pay (Card, USSD, Bank transfer, Opay): how\n * much came in that way, how many payments, its share of all the money\n * received, and how it moved since the period before. A quiet method shows\n * zero, so it is seen rather than missing.\n */\nexport function MethodCards({\n now,\n before,\n currency,\n order,\n labels,\n title = 'How customers paid',\n received = 'of the money received',\n describe = (label: string) => `Money customers paid by ${label.toLowerCase()}, before fees.`,\n hide,\n}: {\n now: readonly GatewayChannelTotal[];\n before: readonly GatewayChannelTotal[] | null;\n currency: string;\n /** The ways to always show, in this order. Customers' checkout methods when left out. */\n order?: readonly string[];\n /** Names for those ways. */\n labels?: Record<string, string>;\n title?: string;\n /** What the share is a share of (\"of the money received\"). */\n received?: string;\n /** The help text for one card. */\n describe?: (label: string, key: string) => string;\n /** Ways to leave out of the row when nothing came in that way. */\n hide?: readonly string[];\n}) {\n const cards = methodCards(now, before, currency, { order, labels }).filter(\n (card) => !(hide?.includes(card.key) && card.payments === 0),\n );\n\n return (\n <section aria-label={title} className=\"adm-grid\">\n {cards.map((card, index) => (\n <Reveal key={card.key} index={index} className=\"adm-span-3\">\n <StatCard\n label={card.label}\n value={\n <CountUp\n value={Number(card.amountMinor) / 100}\n format={(v) => minorToMoney(Math.round(v * 100), currency)}\n />\n }\n sub={\n card.payments === 0\n ? 'No payments this way yet'\n : `${integer.format(card.payments)} ${card.payments === 1 ? 'payment' : 'payments'} · ${card.share}% ${received}`\n }\n hint={describe(card.label, card.key)}\n delta={\n card.change === null\n ? undefined\n : { pct: card.change, caption: 'vs the period before' }\n }\n />\n </Reveal>\n ))}\n </section>\n );\n}\n"],"mappings":"AAkDc;AAjDd,SAAS,SAAS,cAAc;AAChC,SAAS,gBAAgB;AACzB,SAAS,aAAa,oBAAoB;AAE1C,MAAM,UAAU,IAAI,KAAK,aAAa,OAAO;AAQtC,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,WAAW,CAAC,UAAkB,2BAA2B,MAAM,YAAY,CAAC;AAAA,EAC5E;AACF,GAeG;AACD,QAAM,QAAQ,YAAY,KAAK,QAAQ,UAAU,EAAE,OAAO,OAAO,CAAC,EAAE;AAAA,IAClE,CAAC,SAAS,EAAE,MAAM,SAAS,KAAK,GAAG,KAAK,KAAK,aAAa;AAAA,EAC5D;AAEA,SACE,oBAAC,aAAQ,cAAY,OAAO,WAAU,YACnC,gBAAM,IAAI,CAAC,MAAM,UAChB,oBAAC,UAAsB,OAAc,WAAU,cAC7C;AAAA,IAAC;AAAA;AAAA,MACC,OAAO,KAAK;AAAA,MACZ,OACE;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,OAAO,KAAK,WAAW,IAAI;AAAA,UAClC,QAAQ,CAAC,MAAM,aAAa,KAAK,MAAM,IAAI,GAAG,GAAG,QAAQ;AAAA;AAAA,MAC3D;AAAA,MAEF,KACE,KAAK,aAAa,IACd,6BACA,GAAG,QAAQ,OAAO,KAAK,QAAQ,CAAC,IAAI,KAAK,aAAa,IAAI,YAAY,UAAU,SAAM,KAAK,KAAK,KAAK,QAAQ;AAAA,MAEnH,MAAM,SAAS,KAAK,OAAO,KAAK,GAAG;AAAA,MACnC,OACE,KAAK,WAAW,OACZ,SACA,EAAE,KAAK,KAAK,QAAQ,SAAS,uBAAuB;AAAA;AAAA,EAE5D,KApBW,KAAK,GAqBlB,CACD,GACH;AAEJ;","names":[]}
@@ -13,10 +13,12 @@ import {
13
13
  PURPOSE_WORDS,
14
14
  STATUS_WORDS,
15
15
  SUBSCRIPTION_METHOD_WORDS,
16
+ planPaymentTotals,
16
17
  downloadName,
17
18
  minorToMoney,
18
19
  word
19
20
  } from "./ledger-model";
21
+ import { MethodCards } from "./method-cards";
20
22
  import { PlanMixPanel } from "./plan-mix-panel";
21
23
  import { PlanPaymentDrawer } from "./plan-payment-drawer";
22
24
  import { useUrlFilters } from "./use-url-filters";
@@ -31,10 +33,15 @@ const STATUSES = [
31
33
  { value: "failed", label: "Failed" },
32
34
  { value: "pending", label: "Waiting" }
33
35
  ];
36
+ const METHOD_HINTS = {
37
+ wallet: "Plan payments taken from the merchant's Box wallet.",
38
+ gateway: "Plan payments the merchant paid through the Olive payment gateway (card or bank).",
39
+ manual: "Plan payments an admin recorded by hand, for example a bank transfer."
40
+ };
34
41
  const METHODS = [
35
42
  { value: "", label: "Every way of paying" },
36
- { value: "gateway", label: "Card or bank" },
37
- { value: "wallet", label: "Wallet" },
43
+ { value: "gateway", label: "Olive payment gateway" },
44
+ { value: "wallet", label: "Box wallet" },
38
45
  { value: "manual", label: "Recorded by hand" }
39
46
  ];
40
47
  const tone = (status) => status === "succeeded" ? styles.statusGood : status === "failed" ? styles.statusBad : styles.statusWait;
@@ -104,6 +111,24 @@ function SubscriptionsView({
104
111
  }
105
112
  ) })
106
113
  ] }),
114
+ tracked && recurring.collectedByMethod ? /* @__PURE__ */ jsxs(Fragment, { children: [
115
+ /* @__PURE__ */ jsx("h2", { className: styles.sectionTitle, children: "How merchants pay Olive" }),
116
+ /* @__PURE__ */ jsx("p", { className: styles.sectionNote, children: "Plan payments that went through in this period, by the way the merchant paid." }),
117
+ /* @__PURE__ */ jsx(
118
+ MethodCards,
119
+ {
120
+ now: planPaymentTotals(recurring.collectedByMethod, currency),
121
+ before: null,
122
+ currency,
123
+ order: ["wallet", "gateway"],
124
+ labels: SUBSCRIPTION_METHOD_WORDS,
125
+ title: "How merchants pay Olive",
126
+ received: "of the plan income collected",
127
+ describe: (_label, key) => METHOD_HINTS[key] ?? "Plan payments paid this way.",
128
+ hide: ["manual"]
129
+ }
130
+ )
131
+ ] }) : null,
107
132
  tracked && recurring.planBreakdown.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
108
133
  /* @__PURE__ */ jsx("h2", { className: styles.sectionTitle, children: "Who pays what" }),
109
134
  /* @__PURE__ */ jsx("p", { className: styles.sectionNote, children: "How the monthly plan income splits across plans." }),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/revenue/subscriptions-view.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useMemo, useState } from 'react';\nimport { EmptyNote, TableRow, TableShell, Th } from '@withpotter/ui-kit';\nimport {\n platformAdminApi,\n type PlatformRevenue,\n type SubscriptionPaymentRow,\n} from '@withpotter/lib/admin-api';\nimport { useSubscriptionPayments } from '../hooks';\nimport { LoadFailed } from '../audit/load-failed';\nimport { CountUp, Reveal } from '../_fx';\nimport { AdmLoading, FilterSearch, FilterSelect, Filters, Pagination, StatCard, moneyIn } from '../_ui';\nimport { DownloadButton } from './download-button';\nimport {\n PURPOSE_WORDS,\n STATUS_WORDS,\n SUBSCRIPTION_METHOD_WORDS,\n downloadName,\n minorToMoney,\n word,\n} from './ledger-model';\nimport { PlanMixPanel } from './plan-mix-panel';\nimport { PlanPaymentDrawer } from './plan-payment-drawer';\nimport { useUrlFilters } from './use-url-filters';\nimport styles from './ledger.module.css';\nimport table from './revenue.module.css';\n\nconst PAGE_SIZE = 25;\nconst FILTERS = { q: '', status: '', method: '' };\nconst integer = new Intl.NumberFormat('en-US');\n\nconst STATUSES = [\n { value: '', label: 'Every result' },\n { value: 'succeeded', label: 'Paid' },\n { value: 'failed', label: 'Failed' },\n { value: 'pending', label: 'Waiting' },\n];\nconst METHODS = [\n { value: '', label: 'Every way of paying' },\n { value: 'gateway', label: 'Card or bank' },\n { value: 'wallet', label: 'Wallet' },\n { value: 'manual', label: 'Recorded by hand' },\n];\n\nconst tone = (status: string) =>\n status === 'succeeded' ? styles.statusGood : status === 'failed' ? styles.statusBad : styles.statusWait;\n\n/** What merchants pay Olive for their plan: the income figures, who pays what, and every plan payment. */\nexport function SubscriptionsView({\n startDate,\n endDate,\n revenue,\n}: {\n startDate?: string;\n endDate?: string;\n revenue: PlatformRevenue;\n}) {\n const { values, page, set } = useUrlFilters('sp', FILTERS);\n const [search, setSearch] = useState(values.q);\n const [open, setOpen] = useState<SubscriptionPaymentRow | null>(null);\n\n useEffect(() => {\n const timer = setTimeout(() => {\n if (search.trim() !== values.q) set({ q: search.trim() });\n }, 300);\n\n return () => clearTimeout(timer);\n // Only a change the person typed should write to the address.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [search]);\n\n const filters = useMemo(\n () => ({ startDate, endDate, status: values.status, method: values.method, q: values.q, page, limit: PAGE_SIZE }),\n [startDate, endDate, values, page],\n );\n\n const { recurring } = revenue;\n const tracked = recurring.tracked !== false;\n const currency = recurring.currency ?? revenue.currency ?? 'NGN';\n const money = moneyIn(currency);\n const money2 = moneyIn(currency, 2);\n const notSet = 'Not set up yet';\n const why = 'The plan payments have not been connected to this page yet.';\n const payments = useSubscriptionPayments(filters);\n\n return (\n <div>\n <p className={styles.lede}>\n This is money that merchants pay Olive for their plan. It is kept apart from the money customers pay merchants.\n </p>\n\n <section aria-label=\"Plan income\" className=\"adm-grid\">\n <Reveal index={0} className=\"adm-span-3\">\n <StatCard\n label=\"Plan income each month\"\n value={tracked ? <CountUp value={recurring.mrr} format={money} /> : notSet}\n sub={tracked ? `${integer.format(recurring.payingMerchants ?? recurring.activeSubscriptions)} merchants paying` : why}\n hint=\"What merchants on a paid plan are expected to pay Olive every month. Merchants on a free plan, a trial or a pause are not counted.\"\n />\n </Reveal>\n <Reveal index={1} className=\"adm-span-3\">\n <StatCard\n label=\"Plan income over a year\"\n value={tracked ? <CountUp value={recurring.arr} format={money} /> : notSet}\n sub={tracked ? 'Each month times 12' : why}\n hint=\"What plan income adds up to over a year if nothing changes: the monthly figure times 12.\"\n />\n </Reveal>\n <Reveal index={2} className=\"adm-span-3\">\n <StatCard\n label=\"Average per paying merchant\"\n value={tracked ? <CountUp value={recurring.arpa} format={money2} /> : notSet}\n sub={tracked ? 'Each month' : why}\n hint=\"What one paying merchant pays Olive in a month, on average.\"\n />\n </Reveal>\n <Reveal index={3} className=\"adm-span-3\">\n <StatCard\n label=\"Joined and left\"\n value={tracked ? `${integer.format(recurring.newInRange)} joined` : notSet}\n sub={tracked ? `${integer.format(recurring.churnedInRange)} left or cancelled in this period` : why}\n hint=\"Merchants who started a plan in this period, and merchants whose plan was cancelled, ended or switched off.\"\n />\n </Reveal>\n </section>\n\n {tracked && recurring.planBreakdown.length > 0 ? (\n <>\n <h2 className={styles.sectionTitle}>Who pays what</h2>\n <p className={styles.sectionNote}>How the monthly plan income splits across plans.</p>\n <PlanMixPanel planBreakdown={recurring.planBreakdown} money={money} caption={`Each month · ${currency}`} />\n </>\n ) : null}\n\n <h2 className={styles.sectionTitle}>Every plan payment</h2>\n {payments.isError ? (\n <div className={styles.notice}>\n Plan payments are not available on this box yet. Once the subscription module is connected, every payment will\n be listed here.\n </div>\n ) : payments.isLoading || !payments.data ? (\n <AdmLoading label=\"Loading plan payments…\" />\n ) : (\n <>\n <div className={styles.toolbar}>\n <Filters label=\"Filter plan payments\">\n <FilterSearch\n value={search}\n onChange={setSearch}\n placeholder=\"Search a merchant's name or email, or a reference\"\n label=\"Search plan payments\"\n />\n <FilterSelect value={values.status} onChange={(status) => set({ status })} options={STATUSES} label=\"Payment result\" />\n <FilterSelect value={values.method} onChange={(method) => set({ method })} options={METHODS} label=\"Way of paying\" />\n </Filters>\n <DownloadButton\n filename={downloadName('plan-payments', startDate, endDate)}\n load={() => platformAdminApi.subscriptionPaymentsCsv({ ...filters, page: undefined, limit: undefined })}\n />\n </div>\n\n {payments.data.items.length === 0 ? (\n <EmptyNote className={table.empty}>No plan payments match. Try a wider date range or clear a filter.</EmptyNote>\n ) : (\n <TableShell\n minWidth={860}\n density=\"compact\"\n head={\n <tr>\n <Th>When</Th>\n <Th>Merchant</Th>\n <Th>Plan</Th>\n <Th>For</Th>\n <Th>How</Th>\n <Th>Result</Th>\n <Th align=\"right\">Amount</Th>\n </tr>\n }\n >\n {payments.data.items.map((row) => (\n <PlanPaymentRow key={row.id} row={row} onOpen={setOpen} />\n ))}\n </TableShell>\n )}\n <Pagination\n page={page}\n totalPages={Math.max(Math.ceil(payments.data.total / PAGE_SIZE), 1)}\n total={payments.data.total}\n noun=\"plan payments\"\n onPage={(next) => set({ page: next })}\n />\n <PlanPaymentDrawer row={open} onClose={() => setOpen(null)} />\n </>\n )}\n </div>\n );\n}\n\nfunction PlanPaymentRow({ row, onOpen }: { row: SubscriptionPaymentRow; onOpen: (row: SubscriptionPaymentRow) => void }) {\n const when = row.paidAt ?? row.createdAt;\n\n return (\n <TableRow onClick={() => onOpen(row)}>\n <td className={table.num}>\n {new Date(when).toLocaleString(undefined, { day: 'numeric', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' })}\n </td>\n <td>\n <span className={styles.who}>\n <span className={styles.whoName}>{row.merchantName || 'Unknown merchant'}</span>\n {row.merchantEmail ? <span className={styles.whoSub}>{row.merchantEmail}</span> : null}\n </span>\n </td>\n <td className={table.name}>{row.plan.charAt(0).toUpperCase() + row.plan.slice(1)}</td>\n <td>{word(PURPOSE_WORDS, row.purpose)}</td>\n <td>{word(SUBSCRIPTION_METHOD_WORDS, row.method)}</td>\n <td>\n <span className={styles.who}>\n <span className={`${styles.status} ${tone(row.status)}`}>{word(STATUS_WORDS, row.status)}</span>\n {row.status === 'failed' && row.failureReason ? <span className={styles.whoSub}>{row.failureReason}</span> : null}\n </span>\n </td>\n <td className={table.numStrong}>{minorToMoney(row.amountKobo, 'NGN')}</td>\n </TableRow>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/revenue/subscriptions-view.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useMemo, useState } from 'react';\nimport { EmptyNote, TableRow, TableShell, Th } from '@withpotter/ui-kit';\nimport {\n platformAdminApi,\n type PlatformRevenue,\n type SubscriptionPaymentRow,\n} from '@withpotter/lib/admin-api';\nimport { useSubscriptionPayments } from '../hooks';\nimport { LoadFailed } from '../audit/load-failed';\nimport { CountUp, Reveal } from '../_fx';\nimport { AdmLoading, FilterSearch, FilterSelect, Filters, Pagination, StatCard, moneyIn } from '../_ui';\nimport { DownloadButton } from './download-button';\nimport {\n PURPOSE_WORDS,\n STATUS_WORDS,\n SUBSCRIPTION_METHOD_WORDS,\n planPaymentTotals,\n downloadName,\n minorToMoney,\n word,\n} from './ledger-model';\nimport { MethodCards } from './method-cards';\nimport { PlanMixPanel } from './plan-mix-panel';\nimport { PlanPaymentDrawer } from './plan-payment-drawer';\nimport { useUrlFilters } from './use-url-filters';\nimport styles from './ledger.module.css';\nimport table from './revenue.module.css';\n\nconst PAGE_SIZE = 25;\nconst FILTERS = { q: '', status: '', method: '' };\nconst integer = new Intl.NumberFormat('en-US');\n\nconst STATUSES = [\n { value: '', label: 'Every result' },\n { value: 'succeeded', label: 'Paid' },\n { value: 'failed', label: 'Failed' },\n { value: 'pending', label: 'Waiting' },\n];\nconst METHOD_HINTS: Record<string, string> = {\n wallet: \"Plan payments taken from the merchant's Box wallet.\",\n gateway: 'Plan payments the merchant paid through the Olive payment gateway (card or bank).',\n manual: 'Plan payments an admin recorded by hand, for example a bank transfer.',\n};\n\nconst METHODS = [\n { value: '', label: 'Every way of paying' },\n { value: 'gateway', label: 'Olive payment gateway' },\n { value: 'wallet', label: 'Box wallet' },\n { value: 'manual', label: 'Recorded by hand' },\n];\n\nconst tone = (status: string) =>\n status === 'succeeded' ? styles.statusGood : status === 'failed' ? styles.statusBad : styles.statusWait;\n\n/** What merchants pay Olive for their plan: the income figures, who pays what, and every plan payment. */\nexport function SubscriptionsView({\n startDate,\n endDate,\n revenue,\n}: {\n startDate?: string;\n endDate?: string;\n revenue: PlatformRevenue;\n}) {\n const { values, page, set } = useUrlFilters('sp', FILTERS);\n const [search, setSearch] = useState(values.q);\n const [open, setOpen] = useState<SubscriptionPaymentRow | null>(null);\n\n useEffect(() => {\n const timer = setTimeout(() => {\n if (search.trim() !== values.q) set({ q: search.trim() });\n }, 300);\n\n return () => clearTimeout(timer);\n // Only a change the person typed should write to the address.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [search]);\n\n const filters = useMemo(\n () => ({ startDate, endDate, status: values.status, method: values.method, q: values.q, page, limit: PAGE_SIZE }),\n [startDate, endDate, values, page],\n );\n\n const { recurring } = revenue;\n const tracked = recurring.tracked !== false;\n const currency = recurring.currency ?? revenue.currency ?? 'NGN';\n const money = moneyIn(currency);\n const money2 = moneyIn(currency, 2);\n const notSet = 'Not set up yet';\n const why = 'The plan payments have not been connected to this page yet.';\n const payments = useSubscriptionPayments(filters);\n\n return (\n <div>\n <p className={styles.lede}>\n This is money that merchants pay Olive for their plan. It is kept apart from the money customers pay merchants.\n </p>\n\n <section aria-label=\"Plan income\" className=\"adm-grid\">\n <Reveal index={0} className=\"adm-span-3\">\n <StatCard\n label=\"Plan income each month\"\n value={tracked ? <CountUp value={recurring.mrr} format={money} /> : notSet}\n sub={tracked ? `${integer.format(recurring.payingMerchants ?? recurring.activeSubscriptions)} merchants paying` : why}\n hint=\"What merchants on a paid plan are expected to pay Olive every month. Merchants on a free plan, a trial or a pause are not counted.\"\n />\n </Reveal>\n <Reveal index={1} className=\"adm-span-3\">\n <StatCard\n label=\"Plan income over a year\"\n value={tracked ? <CountUp value={recurring.arr} format={money} /> : notSet}\n sub={tracked ? 'Each month times 12' : why}\n hint=\"What plan income adds up to over a year if nothing changes: the monthly figure times 12.\"\n />\n </Reveal>\n <Reveal index={2} className=\"adm-span-3\">\n <StatCard\n label=\"Average per paying merchant\"\n value={tracked ? <CountUp value={recurring.arpa} format={money2} /> : notSet}\n sub={tracked ? 'Each month' : why}\n hint=\"What one paying merchant pays Olive in a month, on average.\"\n />\n </Reveal>\n <Reveal index={3} className=\"adm-span-3\">\n <StatCard\n label=\"Joined and left\"\n value={tracked ? `${integer.format(recurring.newInRange)} joined` : notSet}\n sub={tracked ? `${integer.format(recurring.churnedInRange)} left or cancelled in this period` : why}\n hint=\"Merchants who started a plan in this period, and merchants whose plan was cancelled, ended or switched off.\"\n />\n </Reveal>\n </section>\n\n {tracked && recurring.collectedByMethod ? (\n <>\n <h2 className={styles.sectionTitle}>How merchants pay Olive</h2>\n <p className={styles.sectionNote}>Plan payments that went through in this period, by the way the merchant paid.</p>\n <MethodCards\n now={planPaymentTotals(recurring.collectedByMethod, currency)}\n before={null}\n currency={currency}\n order={['wallet', 'gateway']}\n labels={SUBSCRIPTION_METHOD_WORDS}\n title=\"How merchants pay Olive\"\n received=\"of the plan income collected\"\n describe={(_label, key) => METHOD_HINTS[key] ?? 'Plan payments paid this way.'}\n hide={['manual']}\n />\n </>\n ) : null}\n\n {tracked && recurring.planBreakdown.length > 0 ? (\n <>\n <h2 className={styles.sectionTitle}>Who pays what</h2>\n <p className={styles.sectionNote}>How the monthly plan income splits across plans.</p>\n <PlanMixPanel planBreakdown={recurring.planBreakdown} money={money} caption={`Each month · ${currency}`} />\n </>\n ) : null}\n\n <h2 className={styles.sectionTitle}>Every plan payment</h2>\n {payments.isError ? (\n <div className={styles.notice}>\n Plan payments are not available on this box yet. Once the subscription module is connected, every payment will\n be listed here.\n </div>\n ) : payments.isLoading || !payments.data ? (\n <AdmLoading label=\"Loading plan payments…\" />\n ) : (\n <>\n <div className={styles.toolbar}>\n <Filters label=\"Filter plan payments\">\n <FilterSearch\n value={search}\n onChange={setSearch}\n placeholder=\"Search a merchant's name or email, or a reference\"\n label=\"Search plan payments\"\n />\n <FilterSelect value={values.status} onChange={(status) => set({ status })} options={STATUSES} label=\"Payment result\" />\n <FilterSelect value={values.method} onChange={(method) => set({ method })} options={METHODS} label=\"Way of paying\" />\n </Filters>\n <DownloadButton\n filename={downloadName('plan-payments', startDate, endDate)}\n load={() => platformAdminApi.subscriptionPaymentsCsv({ ...filters, page: undefined, limit: undefined })}\n />\n </div>\n\n {payments.data.items.length === 0 ? (\n <EmptyNote className={table.empty}>No plan payments match. Try a wider date range or clear a filter.</EmptyNote>\n ) : (\n <TableShell\n minWidth={860}\n density=\"compact\"\n head={\n <tr>\n <Th>When</Th>\n <Th>Merchant</Th>\n <Th>Plan</Th>\n <Th>For</Th>\n <Th>How</Th>\n <Th>Result</Th>\n <Th align=\"right\">Amount</Th>\n </tr>\n }\n >\n {payments.data.items.map((row) => (\n <PlanPaymentRow key={row.id} row={row} onOpen={setOpen} />\n ))}\n </TableShell>\n )}\n <Pagination\n page={page}\n totalPages={Math.max(Math.ceil(payments.data.total / PAGE_SIZE), 1)}\n total={payments.data.total}\n noun=\"plan payments\"\n onPage={(next) => set({ page: next })}\n />\n <PlanPaymentDrawer row={open} onClose={() => setOpen(null)} />\n </>\n )}\n </div>\n );\n}\n\nfunction PlanPaymentRow({ row, onOpen }: { row: SubscriptionPaymentRow; onOpen: (row: SubscriptionPaymentRow) => void }) {\n const when = row.paidAt ?? row.createdAt;\n\n return (\n <TableRow onClick={() => onOpen(row)}>\n <td className={table.num}>\n {new Date(when).toLocaleString(undefined, { day: 'numeric', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' })}\n </td>\n <td>\n <span className={styles.who}>\n <span className={styles.whoName}>{row.merchantName || 'Unknown merchant'}</span>\n {row.merchantEmail ? <span className={styles.whoSub}>{row.merchantEmail}</span> : null}\n </span>\n </td>\n <td className={table.name}>{row.plan.charAt(0).toUpperCase() + row.plan.slice(1)}</td>\n <td>{word(PURPOSE_WORDS, row.purpose)}</td>\n <td>{word(SUBSCRIPTION_METHOD_WORDS, row.method)}</td>\n <td>\n <span className={styles.who}>\n <span className={`${styles.status} ${tone(row.status)}`}>{word(STATUS_WORDS, row.status)}</span>\n {row.status === 'failed' && row.failureReason ? <span className={styles.whoSub}>{row.failureReason}</span> : null}\n </span>\n </td>\n <td className={table.numStrong}>{minorToMoney(row.amountKobo, 'NGN')}</td>\n </TableRow>\n );\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@withpotter/pages-admin",
3
- "version": "0.13.0-next.118",
3
+ "version": "0.13.0-next.120",
4
4
  "description": "Box console platform-admin routes: overview, revenue, accounts, tenants, admins, roles, audit.",
5
5
  "license": "UNLICENSED",
6
6
  "sideEffects": false,
@@ -31,9 +31,9 @@
31
31
  "dependencies": {
32
32
  "@radix-ui/react-popover": "^1.1.23",
33
33
  "@tanstack/react-query": "^5.90.20",
34
- "@withpotter/lib": "^0.13.0-next.118",
35
- "@withpotter/platform-admin": "^0.13.0-next.118",
36
- "@withpotter/ui-kit": "^0.13.0-next.118",
34
+ "@withpotter/lib": "^0.13.0-next.120",
35
+ "@withpotter/platform-admin": "^0.13.0-next.120",
36
+ "@withpotter/ui-kit": "^0.13.0-next.120",
37
37
  "lucide-react": "^0.562.0",
38
38
  "motion": "^12.42.2"
39
39
  },