@aglyn/plugins-marketing 1.0.0-beta.220 → 1.0.0-beta.222

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 (106) hide show
  1. package/README.md +4 -1
  2. package/package.json +15 -15
  3. package/src/lib/components/campaign-conversions-card.js +53 -64
  4. package/src/lib/components/campaign-conversions-card.js.map +1 -1
  5. package/src/lib/components/campaign-detail-card.js +2 -1
  6. package/src/lib/components/campaign-detail-card.js.map +1 -1
  7. package/src/lib/components/campaign-edit-drawer.js +1 -1
  8. package/src/lib/components/campaign-edit-drawer.js.map +1 -1
  9. package/src/lib/components/campaign-queries.js +1 -1
  10. package/src/lib/components/campaign-queries.js.map +1 -1
  11. package/src/lib/components/campaign-reach-sections.js +1 -1
  12. package/src/lib/components/campaign-reach-sections.js.map +1 -1
  13. package/src/lib/components/campaign-report-card.js +5 -4
  14. package/src/lib/components/campaign-report-card.js.map +1 -1
  15. package/src/lib/components/campaigns-card.js +1 -1
  16. package/src/lib/components/campaigns-card.js.map +1 -1
  17. package/src/lib/components/email-compose-card.js +1 -1
  18. package/src/lib/components/email-compose-card.js.map +1 -1
  19. package/src/lib/components/email-detail.js +5 -4
  20. package/src/lib/components/email-detail.js.map +1 -1
  21. package/src/lib/components/email-template-report-card.d.ts +53 -0
  22. package/src/lib/components/email-template-report-card.js +469 -0
  23. package/src/lib/components/email-template-report-card.js.map +1 -0
  24. package/src/lib/components/emails-list-card.js +2 -2
  25. package/src/lib/components/emails-list-card.js.map +1 -1
  26. package/src/lib/components/host-experiments-card.component.js +3 -2
  27. package/src/lib/components/host-experiments-card.component.js.map +1 -1
  28. package/src/lib/components/site-runtime.js +3 -1
  29. package/src/lib/components/site-runtime.js.map +1 -1
  30. package/src/lib/declarations.server.d.ts +9 -0
  31. package/src/lib/declarations.server.js +62 -0
  32. package/src/lib/declarations.server.js.map +1 -1
  33. package/src/lib/model/campaign-container.d.ts +503 -0
  34. package/src/lib/model/campaign-container.js +498 -0
  35. package/src/lib/model/campaign-container.js.map +1 -0
  36. package/src/lib/model/campaign-conversions.d.ts +8 -1
  37. package/src/lib/model/campaign-conversions.js +11 -0
  38. package/src/lib/model/campaign-conversions.js.map +1 -1
  39. package/src/lib/model/campaign-report.d.ts +158 -0
  40. package/src/lib/model/campaign-report.js +268 -0
  41. package/src/lib/model/campaign-report.js.map +1 -0
  42. package/src/lib/model/campaign-revenue.d.ts +3 -3
  43. package/src/lib/model/campaign-revenue.js +3 -3
  44. package/src/lib/model/campaign-revenue.js.map +1 -1
  45. package/src/lib/model/campaign-send-time.js +2 -2
  46. package/src/lib/model/campaign-send-time.js.map +1 -1
  47. package/src/lib/model/compile-client-automations.d.ts +2 -2
  48. package/src/lib/model/compile-client-automations.js +3 -3
  49. package/src/lib/model/compile-client-automations.js.map +1 -1
  50. package/src/lib/model/email-record.d.ts +175 -0
  51. package/src/lib/model/email-record.js +206 -0
  52. package/src/lib/model/email-record.js.map +1 -0
  53. package/src/lib/model/site-contract.d.ts +2 -2
  54. package/src/lib/model/site-contract.js.map +1 -1
  55. package/src/lib/model/template-report.d.ts +158 -0
  56. package/src/lib/model/template-report.js +249 -0
  57. package/src/lib/model/template-report.js.map +1 -0
  58. package/src/lib/plugin.js +16 -0
  59. package/src/lib/plugin.js.map +1 -1
  60. package/src/lib/preview-props.js.map +1 -1
  61. package/src/lib/server/campaign-attribution-store.d.ts +55 -0
  62. package/src/lib/server/campaign-attribution-store.js +98 -0
  63. package/src/lib/server/campaign-attribution-store.js.map +1 -0
  64. package/src/lib/server/campaign-conversion-attribution.d.ts +283 -0
  65. package/src/lib/server/campaign-conversion-attribution.js +447 -0
  66. package/src/lib/server/campaign-conversion-attribution.js.map +1 -0
  67. package/src/lib/server/campaign-manage.js +1 -1
  68. package/src/lib/server/campaign-manage.js.map +1 -1
  69. package/src/lib/server/campaign-org-refs.js +1 -1
  70. package/src/lib/server/campaign-org-refs.js.map +1 -1
  71. package/src/lib/server/campaign-recipients.js +7 -7
  72. package/src/lib/server/campaign-recipients.js.map +1 -1
  73. package/src/lib/server/campaign-send.js +25 -19
  74. package/src/lib/server/campaign-send.js.map +1 -1
  75. package/src/lib/server/conversion-creditor.d.ts +18 -0
  76. package/src/lib/server/conversion-creditor.js +131 -0
  77. package/src/lib/server/conversion-creditor.js.map +1 -0
  78. package/src/lib/server/email-campaign-touch.d.ts +81 -0
  79. package/src/lib/server/email-campaign-touch.js +227 -0
  80. package/src/lib/server/email-campaign-touch.js.map +1 -0
  81. package/src/lib/server/email-events.js +8 -6
  82. package/src/lib/server/email-events.js.map +1 -1
  83. package/src/lib/server/email-revenue-attribution.d.ts +186 -0
  84. package/src/lib/server/email-revenue-attribution.js +322 -0
  85. package/src/lib/server/email-revenue-attribution.js.map +1 -0
  86. package/src/lib/server/email-send-record-index.d.ts +34 -0
  87. package/src/lib/server/email-send-record-index.js +90 -0
  88. package/src/lib/server/email-send-record-index.js.map +1 -0
  89. package/src/lib/server/get-client-automations.js.map +1 -1
  90. package/src/lib/server/marketing-figures.js +3 -3
  91. package/src/lib/server/marketing-figures.js.map +1 -1
  92. package/src/lib/server/send-tally.d.ts +32 -0
  93. package/src/lib/server/send-tally.js +54 -0
  94. package/src/lib/server/send-tally.js.map +1 -0
  95. package/src/lib/testing/stand-in-person-records.d.ts +35 -0
  96. package/src/lib/testing/stand-in-person-records.js +67 -0
  97. package/src/lib/testing/stand-in-person-records.js.map +1 -0
  98. package/src/lib/testing/stand-in-record-counts.d.ts +26 -0
  99. package/src/lib/testing/stand-in-record-counts.js +70 -0
  100. package/src/lib/testing/stand-in-record-counts.js.map +1 -0
  101. package/src/lib/transfer/campaigns-package.d.ts +72 -0
  102. package/src/lib/transfer/campaigns-package.js +145 -0
  103. package/src/lib/transfer/campaigns-package.js.map +1 -0
  104. package/src/lib/transfer/campaigns-package.server.d.ts +33 -0
  105. package/src/lib/transfer/campaigns-package.server.js +305 -0
  106. package/src/lib/transfer/campaigns-package.server.js.map +1 -0
@@ -0,0 +1,469 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { _ as _extends } from "@swc/helpers/_/_extends";
18
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
19
+ import { pluginDocsHelp } from "@aglyn/aglyn";
20
+ import { pluginRecordHref } from "@aglyn/aglyn/plugin-manager/plugin-record-routes";
21
+ import { mdiBullhornOutline, mdiEyeOutline } from "@aglyn/shared-data-mdi";
22
+ import { AppLink, CardDisplay, MdiIcon } from "@aglyn/shared-ui-jsx";
23
+ import { Figure, RateRow, Section } from "@aglyn/shared-ui-jsx/components/measured-figures.component";
24
+ import RowActionsMenu from "@aglyn/shared-ui-jsx/components/row-actions-menu.component";
25
+ import { ScrollTable } from "@aglyn/shared-ui-jsx/components/scroll-table.component";
26
+ import { useConsoleHostRoute, useFirestore, useFirestoreCollection, useOrgDataScope } from "@aglyn/tenant-feature-instance";
27
+ import { Alert, Chip, Divider, Stack, TableBody, TableCell, TableHead, TableRow, Typography } from "@mui/material";
28
+ import { collection, documentId, limit, orderBy, query, where } from "firebase/firestore";
29
+ import { useRouter } from "next/navigation";
30
+ import { useMemo } from "react";
31
+ import { CAMPAIGN_SEND_CONTAINER_FIELD, campaignSendDisplay, campaignSendVisibleTo, orgCampaignSendsPath } from "../model/campaign-container.js";
32
+ import { emailSendTimeMs } from "../model/email-record.js";
33
+ import { templateReport } from "../model/template-report.js";
34
+ /**
35
+ * How many of a template's messages one read covers.
36
+ *
37
+ * A CEILING on the read, not a page size: the report is a single figure per
38
+ * quantity, so there is nothing to page through. Past it every total is a
39
+ * floor and the report says so — the same treatment `audienceSizeTruncated`
40
+ * gets, and for the same reason.
41
+ */ export const TEMPLATE_CAMPAIGN_CEILING = 100;
42
+ const reportDocsHelp = pluginDocsHelp('designedEmails', {
43
+ anchor: '#send-it',
44
+ excerpt: 'Every message sent from this template, and what those messages did — ' + 'each rate over the population it was measured against.'
45
+ });
46
+ /**
47
+ * WHAT ONE TEMPLATE'S EMAILS DID, SUMMED (AGL-3080).
48
+ *
49
+ * The template is the Email plugin's; the messages sent from it are this
50
+ * plugin's campaign sends. So the template's page hosts a zone and this card
51
+ * fills it: the page names the template, and the sends are read here, where
52
+ * their collection is owned.
53
+ *
54
+ * ## What this card reads
55
+ *
56
+ * The messages naming this template, bounded at
57
+ * {@link TEMPLATE_CAMPAIGN_CEILING}. The recipients table is its own card and
58
+ * its own request, so a reader who came for the totals does not pay for the
59
+ * delivery-log query.
60
+ */ export function EmailTemplateReportCard(props) {
61
+ var _ref;
62
+ const { hostId, screenId, basePath } = props;
63
+ const { orgSlug, subdomain } = useConsoleHostRoute(hostId);
64
+ /*
65
+ * A campaign's page is published on the record-route seam like every other
66
+ * kind, and read the same way here: one place decides where a `campaign`
67
+ * is read. Null before the console's URL has resolved.
68
+ */ const campaignHref = (campaignId)=>orgSlug && subdomain ? pluginRecordHref('campaign', {
69
+ orgSlug,
70
+ host: subdomain
71
+ }, campaignId) : null;
72
+ const firestore = useFirestore();
73
+ const router = useRouter();
74
+ /*
75
+ * The messages sent from this template, ordered by DOCUMENT ID.
76
+ *
77
+ * Not by date, and that is forced rather than chosen: a sent message
78
+ * carries `sentAt`, a scheduled one carries `sendAtMs`, and no writer
79
+ * stamps a `createdAt` — so `orderBy` on either would not mis-sort this
80
+ * list, it would DROP every message written by the other branch. Ordering
81
+ * on the document id is the one ordering every message satisfies, and the
82
+ * rows are sorted by date below over a window this component already holds.
83
+ * The index the filters need is `visibleTo` + `templateScreenId`, declared
84
+ * in `cloud/firebase-firestore.indexes.json`.
85
+ *
86
+ * One MORE than the ceiling, so truncation is a fact rather than a guess.
87
+ *
88
+ * The sends are the ORGANIZATION'S, each stamped with the one site it is
89
+ * sent as. The `visibleTo` clause names this site: it keeps a sibling
90
+ * site's sends of a same-id design out, and it is what makes the read
91
+ * provable for a collaborator scoped to this site — the rules evaluate a
92
+ * list per document, and an unfiltered one is refused whole. Nothing is
93
+ * read until the site's org is known.
94
+ */ const { orgId } = useOrgDataScope({
95
+ hostId
96
+ });
97
+ const sendScope = useMemo(()=>campaignSendVisibleTo(hostId), [
98
+ hostId
99
+ ]);
100
+ const { data: messageDocs } = useFirestoreCollection(()=>orgId ? query(collection(firestore, ...orgCampaignSendsPath(orgId)), where('visibleTo', 'array-contains-any', sendScope), where('templateScreenId', '==', screenId), orderBy(documentId()), limit(TEMPLATE_CAMPAIGN_CEILING + 1)) : null, [
101
+ firestore,
102
+ orgId,
103
+ sendScope,
104
+ screenId
105
+ ], {
106
+ idField: '$id'
107
+ });
108
+ const truncated = ((_ref = messageDocs == null ? void 0 : messageDocs.length) != null ? _ref : 0) > TEMPLATE_CAMPAIGN_CEILING;
109
+ const messages = useMemo(()=>(messageDocs != null ? messageDocs : []).slice(0, TEMPLATE_CAMPAIGN_CEILING).map((message)=>{
110
+ var _message_status, _message_status1, _message_audience;
111
+ return _extends({
112
+ campaignId: String(message.$id),
113
+ subject: String(message.subject || 'Untitled email'),
114
+ /*
115
+ * A message that has not gone out has NO send time, and that is
116
+ * what `null` means here — not "unknown". The report measures
117
+ * engagement over sent messages only, so a scheduled one
118
+ * contributes to the list below and to no figure above it.
119
+ */ sentAtMs: String((_message_status = message.status) != null ? _message_status : '') === 'sent' ? emailSendTimeMs(message) || null : null,
120
+ status: String((_message_status1 = message.status) != null ? _message_status1 : 'sent'),
121
+ audience: String((_message_audience = message.audience) != null ? _message_audience : '')
122
+ }, message.listId ? {
123
+ listId: String(message.listId)
124
+ } : {}, message.listName ? {
125
+ listName: String(message.listName)
126
+ } : {}, {
127
+ stats: message.stats,
128
+ scheduledForMs: emailSendTimeMs(message)
129
+ }, message[CAMPAIGN_SEND_CONTAINER_FIELD] ? {
130
+ emailCampaignId: String(message[CAMPAIGN_SEND_CONTAINER_FIELD])
131
+ } : {});
132
+ }), [
133
+ messageDocs
134
+ ]);
135
+ const orderedMessages = useMemo(()=>[
136
+ ...messages
137
+ ].sort((a, b)=>b.scheduledForMs - a.scheduledForMs), [
138
+ messages
139
+ ]);
140
+ const report = useMemo(()=>templateReport(messages, truncated), [
141
+ messages,
142
+ truncated
143
+ ]);
144
+ const messageHref = (message)=>`${basePath}/messages/${message.campaignId}`;
145
+ /**
146
+ * What one message sent from this template can be opened into.
147
+ *
148
+ * Its own report, and the campaign that grouped it. The template is the page
149
+ * the reader is standing on, so it is not offered a third time. A message
150
+ * sent before campaigns existed belongs to no container, and the item says
151
+ * so rather than vanishing.
152
+ */ const messageActions = (message)=>[
153
+ {
154
+ key: 'details',
155
+ label: 'Open report',
156
+ icon: /*#__PURE__*/ _jsx(MdiIcon, {
157
+ path: mdiEyeOutline.path,
158
+ size: 0.8
159
+ }),
160
+ href: messageHref(message)
161
+ },
162
+ {
163
+ key: 'campaign',
164
+ label: 'Open its campaign',
165
+ icon: /*#__PURE__*/ _jsx(MdiIcon, {
166
+ path: mdiBullhornOutline.path,
167
+ size: 0.8
168
+ }),
169
+ // The one href here that is not under the Emails page's base path.
170
+ href: message.emailCampaignId && campaignHref(message.emailCampaignId) || undefined,
171
+ disabled: !message.emailCampaignId || !campaignHref(message.emailCampaignId),
172
+ disabledReason: message.emailCampaignId ? 'This site’s console URL has not resolved yet' : 'Sent before campaigns grouped their emails, so it belongs to none'
173
+ }
174
+ ];
175
+ return /*#__PURE__*/ _jsx(CardDisplay, {
176
+ header: 'Sent from this template',
177
+ help: reportDocsHelp,
178
+ contentGutterX: true,
179
+ contentGutterY: true,
180
+ children: /*#__PURE__*/ _jsxs(Stack, {
181
+ spacing: 3,
182
+ children: [
183
+ report.caveats.map((caveat)=>/*#__PURE__*/ _jsx(Alert, {
184
+ severity: "info",
185
+ children: caveat.message
186
+ }, caveat.id)),
187
+ /*#__PURE__*/ _jsx(Section, {
188
+ title: "Delivery",
189
+ children: /*#__PURE__*/ _jsxs(Stack, {
190
+ direction: "row",
191
+ spacing: 4,
192
+ useFlexGap: true,
193
+ sx: {
194
+ flexWrap: 'wrap'
195
+ },
196
+ children: [
197
+ /*#__PURE__*/ _jsx(Figure, {
198
+ label: "Emails",
199
+ value: report.sentCampaigns,
200
+ note: "sent from this template"
201
+ }),
202
+ /*#__PURE__*/ _jsx(Figure, {
203
+ label: "Addressed",
204
+ value: report.recipients,
205
+ note: "after each send's cap"
206
+ }),
207
+ /*#__PURE__*/ _jsx(Figure, {
208
+ label: "Sent",
209
+ value: report.sent,
210
+ note: "accepted by the provider"
211
+ }),
212
+ /*#__PURE__*/ _jsx(Figure, {
213
+ label: "Delivered",
214
+ value: report.delivered,
215
+ note: "accepted by the receiving server"
216
+ }),
217
+ /*#__PURE__*/ _jsx(Figure, {
218
+ label: "Bounced",
219
+ value: report.bounced,
220
+ note: "of sent"
221
+ }),
222
+ /*#__PURE__*/ _jsx(Figure, {
223
+ label: "Marked as spam",
224
+ value: report.complained,
225
+ note: "of delivered"
226
+ })
227
+ ]
228
+ })
229
+ }),
230
+ /*#__PURE__*/ _jsx(Divider, {}),
231
+ /*#__PURE__*/ _jsx(Section, {
232
+ title: "Engagement",
233
+ children: /*#__PURE__*/ _jsxs(Stack, {
234
+ direction: "row",
235
+ spacing: 4,
236
+ useFlexGap: true,
237
+ sx: {
238
+ flexWrap: 'wrap'
239
+ },
240
+ children: [
241
+ /*#__PURE__*/ _jsx(Figure, {
242
+ label: "Opens",
243
+ value: report.opens,
244
+ note: "every open, repeats included"
245
+ }),
246
+ /*#__PURE__*/ _jsx(Figure, {
247
+ label: "Readers who opened",
248
+ value: report.uniqueOpens,
249
+ note: "distinct recipients"
250
+ }),
251
+ /*#__PURE__*/ _jsx(Figure, {
252
+ label: "Clicks",
253
+ value: report.clicks,
254
+ note: "every click, repeats included"
255
+ }),
256
+ /*#__PURE__*/ _jsx(Figure, {
257
+ label: "Readers who clicked",
258
+ value: report.uniqueClicks,
259
+ note: "distinct recipients"
260
+ }),
261
+ /*#__PURE__*/ _jsx(Figure, {
262
+ label: "Unsubscribed",
263
+ value: report.unsubscribes,
264
+ note: "through these emails' links"
265
+ })
266
+ ]
267
+ })
268
+ }),
269
+ /*#__PURE__*/ _jsx(Divider, {}),
270
+ /*#__PURE__*/ _jsx(Section, {
271
+ title: "Rates",
272
+ children: /*#__PURE__*/ _jsxs(Stack, {
273
+ spacing: 1,
274
+ children: [
275
+ /*#__PURE__*/ _jsx(RateRow, {
276
+ label: "Delivery rate",
277
+ rate: report.rates.delivery
278
+ }),
279
+ /*#__PURE__*/ _jsx(RateRow, {
280
+ label: "Open rate",
281
+ rate: report.rates.open
282
+ }),
283
+ /*#__PURE__*/ _jsx(RateRow, {
284
+ label: "Click rate",
285
+ rate: report.rates.click
286
+ }),
287
+ /*#__PURE__*/ _jsx(RateRow, {
288
+ label: "Click-to-open rate",
289
+ rate: report.rates.clickToOpen
290
+ }),
291
+ /*#__PURE__*/ _jsx(RateRow, {
292
+ label: "Bounce rate",
293
+ rate: report.rates.bounce
294
+ }),
295
+ /*#__PURE__*/ _jsx(RateRow, {
296
+ label: "Complaint rate",
297
+ rate: report.rates.complaint
298
+ }),
299
+ /*#__PURE__*/ _jsx(RateRow, {
300
+ label: "Unsubscribe rate",
301
+ rate: report.rates.unsubscribe
302
+ })
303
+ ]
304
+ })
305
+ }),
306
+ /*#__PURE__*/ _jsx(Divider, {}),
307
+ /*#__PURE__*/ _jsx(Section, {
308
+ title: "Who this went to",
309
+ children: report.audiences.length ? /*#__PURE__*/ _jsxs(_Fragment, {
310
+ children: [
311
+ /*#__PURE__*/ _jsxs(ScrollTable, {
312
+ size: "small",
313
+ children: [
314
+ /*#__PURE__*/ _jsx(TableHead, {
315
+ children: /*#__PURE__*/ _jsxs(TableRow, {
316
+ children: [
317
+ /*#__PURE__*/ _jsx(TableCell, {
318
+ children: 'Audience'
319
+ }),
320
+ /*#__PURE__*/ _jsx(TableCell, {
321
+ align: "right",
322
+ children: 'Emails'
323
+ }),
324
+ /*#__PURE__*/ _jsx(TableCell, {
325
+ align: "right",
326
+ children: 'Addressed'
327
+ })
328
+ ]
329
+ })
330
+ }),
331
+ /*#__PURE__*/ _jsx(TableBody, {
332
+ children: report.audiences.map((audience)=>/*#__PURE__*/ _jsxs(TableRow, {
333
+ children: [
334
+ /*#__PURE__*/ _jsx(TableCell, {
335
+ children: audience.label
336
+ }),
337
+ /*#__PURE__*/ _jsx(TableCell, {
338
+ align: "right",
339
+ children: audience.campaigns.toLocaleString()
340
+ }),
341
+ /*#__PURE__*/ _jsx(TableCell, {
342
+ align: "right",
343
+ sx: {
344
+ fontWeight: 'bold'
345
+ },
346
+ children: audience.addressed.toLocaleString()
347
+ })
348
+ ]
349
+ }, audience.id))
350
+ })
351
+ ]
352
+ }),
353
+ /*#__PURE__*/ _jsx(Typography, {
354
+ variant: "caption",
355
+ color: "text.secondary",
356
+ children: 'Each list is named as it was when the email was sent, so ' + 'renaming or deleting a list does not rewrite what a past ' + 'send went to.'
357
+ })
358
+ ]
359
+ }) : /*#__PURE__*/ _jsx(Typography, {
360
+ variant: "body2",
361
+ color: "text.secondary",
362
+ children: 'No email sent from this template has gone out yet.'
363
+ })
364
+ }),
365
+ orderedMessages.length ? /*#__PURE__*/ _jsxs(_Fragment, {
366
+ children: [
367
+ /*#__PURE__*/ _jsx(Divider, {}),
368
+ /*#__PURE__*/ _jsx(Section, {
369
+ title: "Emails using this template",
370
+ children: /*#__PURE__*/ _jsxs(ScrollTable, {
371
+ size: "small",
372
+ children: [
373
+ /*#__PURE__*/ _jsx(TableHead, {
374
+ children: /*#__PURE__*/ _jsxs(TableRow, {
375
+ children: [
376
+ /*#__PURE__*/ _jsx(TableCell, {
377
+ children: 'Subject'
378
+ }),
379
+ /*#__PURE__*/ _jsx(TableCell, {
380
+ children: 'State'
381
+ }),
382
+ /*#__PURE__*/ _jsx(TableCell, {
383
+ children: 'When'
384
+ }),
385
+ /*#__PURE__*/ _jsx(TableCell, {
386
+ align: "right",
387
+ children: 'Addressed'
388
+ }),
389
+ /*#__PURE__*/ _jsx(TableCell, {
390
+ align: "right",
391
+ children: 'Opens'
392
+ }),
393
+ /*#__PURE__*/ _jsx(TableCell, {
394
+ align: "right",
395
+ children: 'Clicks'
396
+ }),
397
+ /*#__PURE__*/ _jsx(TableCell, {
398
+ align: "right"
399
+ })
400
+ ]
401
+ })
402
+ }),
403
+ /*#__PURE__*/ _jsx(TableBody, {
404
+ children: orderedMessages.map((message)=>{
405
+ var _ref, _ref1, _ref2;
406
+ var _message_stats, _message_stats1, _message_stats2;
407
+ return /*#__PURE__*/ _jsxs(TableRow, {
408
+ hover: true,
409
+ onClick: ()=>router.push(messageHref(message)),
410
+ sx: {
411
+ cursor: 'pointer'
412
+ },
413
+ children: [
414
+ /*#__PURE__*/ _jsx(TableCell, {
415
+ children: /*#__PURE__*/ _jsx(AppLink, {
416
+ href: messageHref(message),
417
+ onClick: (event)=>event.stopPropagation(),
418
+ children: message.subject
419
+ })
420
+ }),
421
+ /*#__PURE__*/ _jsx(TableCell, {
422
+ children: /*#__PURE__*/ _jsx(Chip, {
423
+ size: "small",
424
+ label: campaignSendDisplay(message).label
425
+ })
426
+ }),
427
+ /*#__PURE__*/ _jsx(TableCell, {
428
+ children: message.scheduledForMs ? new Date(message.scheduledForMs).toLocaleString() : '—'
429
+ }),
430
+ /*#__PURE__*/ _jsx(TableCell, {
431
+ align: "right",
432
+ children: Number((_ref = (_message_stats = message.stats) == null ? void 0 : _message_stats.recipients) != null ? _ref : 0).toLocaleString()
433
+ }),
434
+ /*#__PURE__*/ _jsx(TableCell, {
435
+ align: "right",
436
+ children: Number((_ref1 = (_message_stats1 = message.stats) == null ? void 0 : _message_stats1.opens) != null ? _ref1 : 0).toLocaleString()
437
+ }),
438
+ /*#__PURE__*/ _jsx(TableCell, {
439
+ align: "right",
440
+ children: Number((_ref2 = (_message_stats2 = message.stats) == null ? void 0 : _message_stats2.clicks) != null ? _ref2 : 0).toLocaleString()
441
+ }),
442
+ /*#__PURE__*/ _jsx(TableCell, {
443
+ align: "right",
444
+ sx: {
445
+ width: 56
446
+ },
447
+ onClick: (event)=>event.stopPropagation(),
448
+ children: /*#__PURE__*/ _jsx(RowActionsMenu, {
449
+ label: String(message.subject),
450
+ items: messageActions(message)
451
+ })
452
+ })
453
+ ]
454
+ }, message.campaignId);
455
+ })
456
+ })
457
+ ]
458
+ })
459
+ })
460
+ ]
461
+ }) : null
462
+ ]
463
+ })
464
+ });
465
+ }
466
+ EmailTemplateReportCard.displayName = 'EmailTemplateReportCard';
467
+ export default EmailTemplateReportCard;
468
+
469
+ //# sourceMappingURL=email-template-report-card.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketing/src/lib/components/email-template-report-card.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport { pluginRecordHref } from '@aglyn/aglyn/plugin-manager/plugin-record-routes'\nimport { mdiBullhornOutline, mdiEyeOutline } from '@aglyn/shared-data-mdi'\nimport { AppLink, CardDisplay, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport {\n Figure,\n RateRow,\n Section,\n} from '@aglyn/shared-ui-jsx/components/measured-figures.component'\nimport RowActionsMenu, {\n type RowActionsMenuItem,\n} from '@aglyn/shared-ui-jsx/components/row-actions-menu.component'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport {\n useConsoleHostRoute,\n useFirestore,\n useFirestoreCollection,\n useOrgDataScope,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Chip,\n Divider,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n Typography,\n} from '@mui/material'\nimport {\n collection,\n documentId,\n limit,\n orderBy,\n query,\n where,\n} from 'firebase/firestore'\nimport { useRouter } from 'next/navigation'\nimport { useMemo } from 'react'\nimport {\n CAMPAIGN_SEND_CONTAINER_FIELD,\n campaignSendDisplay,\n campaignSendVisibleTo,\n orgCampaignSendsPath,\n} from '../model/campaign-container'\nimport { emailSendTimeMs } from '../model/email-record'\nimport { templateReport, type TemplateCampaign } from '../model/template-report'\n\n/**\n * How many of a template's messages one read covers.\n *\n * A CEILING on the read, not a page size: the report is a single figure per\n * quantity, so there is nothing to page through. Past it every total is a\n * floor and the report says so — the same treatment `audienceSizeTruncated`\n * gets, and for the same reason.\n */\nexport const TEMPLATE_CAMPAIGN_CEILING = 100\n\nconst reportDocsHelp = pluginDocsHelp('designedEmails', {\n anchor: '#send-it',\n excerpt:\n 'Every message sent from this template, and what those messages did — ' +\n 'each rate over the population it was measured against.',\n})\n\n/** What the Email plugin's template page hands its report zone. */\nexport interface EmailTemplateReportCardProps {\n hostId: string\n /** The `kind: 'email'` screen document the template page is about. */\n screenId: string\n /** The Emails page's own path, which every message link hangs beneath. */\n basePath: string\n}\n\n/**\n * A message row on this card: what the report needs, plus what the ROW needs.\n *\n * `TemplateCampaign` is the report's shape and stays that — it is the input to\n * `templateReport`, and a field the figures do not read has no business in it.\n * The send time this table sorts on and the campaign its row menu offers are\n * both properties of the row rather than of the report.\n */\ntype TemplateMessage = TemplateCampaign & {\n scheduledForMs: number\n /** Absent on a message written before campaigns grouped their emails. */\n emailCampaignId?: string\n}\n\n/**\n * WHAT ONE TEMPLATE'S EMAILS DID, SUMMED (AGL-3080).\n *\n * The template is the Email plugin's; the messages sent from it are this\n * plugin's campaign sends. So the template's page hosts a zone and this card\n * fills it: the page names the template, and the sends are read here, where\n * their collection is owned.\n *\n * ## What this card reads\n *\n * The messages naming this template, bounded at\n * {@link TEMPLATE_CAMPAIGN_CEILING}. The recipients table is its own card and\n * its own request, so a reader who came for the totals does not pay for the\n * delivery-log query.\n */\nexport function EmailTemplateReportCard(props: EmailTemplateReportCardProps) {\n const { hostId, screenId, basePath } = props\n const { orgSlug, subdomain } = useConsoleHostRoute(hostId)\n /*\n * A campaign's page is published on the record-route seam like every other\n * kind, and read the same way here: one place decides where a `campaign`\n * is read. Null before the console's URL has resolved.\n */\n const campaignHref = (campaignId: string): string | null =>\n orgSlug && subdomain\n ? pluginRecordHref('campaign', { orgSlug, host: subdomain }, campaignId)\n : null\n const firestore = useFirestore()\n const router = useRouter()\n\n /*\n * The messages sent from this template, ordered by DOCUMENT ID.\n *\n * Not by date, and that is forced rather than chosen: a sent message\n * carries `sentAt`, a scheduled one carries `sendAtMs`, and no writer\n * stamps a `createdAt` — so `orderBy` on either would not mis-sort this\n * list, it would DROP every message written by the other branch. Ordering\n * on the document id is the one ordering every message satisfies, and the\n * rows are sorted by date below over a window this component already holds.\n * The index the filters need is `visibleTo` + `templateScreenId`, declared\n * in `cloud/firebase-firestore.indexes.json`.\n *\n * One MORE than the ceiling, so truncation is a fact rather than a guess.\n *\n * The sends are the ORGANIZATION'S, each stamped with the one site it is\n * sent as. The `visibleTo` clause names this site: it keeps a sibling\n * site's sends of a same-id design out, and it is what makes the read\n * provable for a collaborator scoped to this site — the rules evaluate a\n * list per document, and an unfiltered one is refused whole. Nothing is\n * read until the site's org is known.\n */\n const { orgId } = useOrgDataScope({ hostId })\n const sendScope = useMemo(() => campaignSendVisibleTo(hostId), [hostId])\n const { data: messageDocs } = useFirestoreCollection<any>(\n () =>\n orgId\n ? query(\n collection(firestore, ...orgCampaignSendsPath(orgId)),\n where('visibleTo', 'array-contains-any', sendScope),\n where('templateScreenId', '==', screenId),\n orderBy(documentId()),\n limit(TEMPLATE_CAMPAIGN_CEILING + 1),\n )\n : null,\n [firestore, orgId, sendScope, screenId],\n { idField: '$id' },\n )\n\n const truncated = (messageDocs?.length ?? 0) > TEMPLATE_CAMPAIGN_CEILING\n const messages = useMemo<TemplateMessage[]>(\n () =>\n (messageDocs ?? [])\n .slice(0, TEMPLATE_CAMPAIGN_CEILING)\n .map((message: any) => ({\n campaignId: String(message.$id),\n subject: String(message.subject || 'Untitled email'),\n /*\n * A message that has not gone out has NO send time, and that is\n * what `null` means here — not \"unknown\". The report measures\n * engagement over sent messages only, so a scheduled one\n * contributes to the list below and to no figure above it.\n */\n sentAtMs:\n String(message.status ?? '') === 'sent'\n ? emailSendTimeMs(message) || null\n : null,\n status: String(message.status ?? 'sent'),\n audience: String(message.audience ?? ''),\n ...(message.listId ? { listId: String(message.listId) } : {}),\n ...(message.listName ? { listName: String(message.listName) } : {}),\n stats: message.stats,\n scheduledForMs: emailSendTimeMs(message),\n /*\n * The campaign this message belongs to, carried through so the row\n * can offer it. A message written before campaigns grouped anything\n * names no container, which is why it is optional rather than\n * defaulted to the message's own id — a menu item that navigated to\n * the message you are already looking at would be worse than one\n * that says the message belongs to no campaign.\n */\n ...(message[CAMPAIGN_SEND_CONTAINER_FIELD]\n ? {\n emailCampaignId: String(\n message[CAMPAIGN_SEND_CONTAINER_FIELD],\n ),\n }\n : {}),\n })),\n [messageDocs],\n )\n const orderedMessages = useMemo(\n () => [...messages].sort((a, b) => b.scheduledForMs - a.scheduledForMs),\n [messages],\n )\n const report = useMemo(\n () => templateReport(messages, truncated),\n [messages, truncated],\n )\n\n const messageHref = (message: TemplateMessage) =>\n `${basePath}/messages/${message.campaignId}`\n\n /**\n * What one message sent from this template can be opened into.\n *\n * Its own report, and the campaign that grouped it. The template is the page\n * the reader is standing on, so it is not offered a third time. A message\n * sent before campaigns existed belongs to no container, and the item says\n * so rather than vanishing.\n */\n const messageActions = (message: TemplateMessage): RowActionsMenuItem[] => [\n {\n key: 'details',\n label: 'Open report',\n icon: <MdiIcon path={mdiEyeOutline.path} size={0.8} />,\n href: messageHref(message),\n },\n {\n key: 'campaign',\n label: 'Open its campaign',\n icon: <MdiIcon path={mdiBullhornOutline.path} size={0.8} />,\n // The one href here that is not under the Emails page's base path.\n href:\n (message.emailCampaignId && campaignHref(message.emailCampaignId)) ||\n undefined,\n disabled:\n !message.emailCampaignId || !campaignHref(message.emailCampaignId),\n disabledReason: message.emailCampaignId\n ? 'This site’s console URL has not resolved yet'\n : 'Sent before campaigns grouped their emails, so it belongs to none',\n },\n ]\n\n return (\n <CardDisplay\n header={'Sent from this template'}\n help={reportDocsHelp}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={3}>\n {report.caveats.map((caveat) => (\n <Alert key={caveat.id} severity=\"info\">\n {caveat.message}\n </Alert>\n ))}\n\n {/*==========================================\n * WHAT HAPPENED TO THE MAIL, SUMMED.\n *\n * Counts first and rates second, because a rate is only readable\n * once you know what it was taken over — and because a count here\n * covers every message while a rate covers only the messages that\n * recorded its denominator. The rate labels say which.\n *=========================================*/}\n <Section title=\"Delivery\">\n <Stack\n direction=\"row\"\n spacing={4}\n useFlexGap\n sx={{ flexWrap: 'wrap' }}\n >\n <Figure\n label=\"Emails\"\n value={report.sentCampaigns}\n note=\"sent from this template\"\n />\n <Figure\n label=\"Addressed\"\n value={report.recipients}\n note=\"after each send's cap\"\n />\n <Figure\n label=\"Sent\"\n value={report.sent}\n note=\"accepted by the provider\"\n />\n <Figure\n label=\"Delivered\"\n value={report.delivered}\n note=\"accepted by the receiving server\"\n />\n <Figure label=\"Bounced\" value={report.bounced} note=\"of sent\" />\n <Figure\n label=\"Marked as spam\"\n value={report.complained}\n note=\"of delivered\"\n />\n </Stack>\n </Section>\n\n <Divider />\n\n <Section title=\"Engagement\">\n <Stack\n direction=\"row\"\n spacing={4}\n useFlexGap\n sx={{ flexWrap: 'wrap' }}\n >\n <Figure\n label=\"Opens\"\n value={report.opens}\n note=\"every open, repeats included\"\n />\n <Figure\n label=\"Readers who opened\"\n value={report.uniqueOpens}\n note=\"distinct recipients\"\n />\n <Figure\n label=\"Clicks\"\n value={report.clicks}\n note=\"every click, repeats included\"\n />\n <Figure\n label=\"Readers who clicked\"\n value={report.uniqueClicks}\n note=\"distinct recipients\"\n />\n <Figure\n label=\"Unsubscribed\"\n value={report.unsubscribes}\n note=\"through these emails' links\"\n />\n </Stack>\n </Section>\n\n <Divider />\n\n <Section title=\"Rates\">\n <Stack spacing={1}>\n <RateRow label=\"Delivery rate\" rate={report.rates.delivery} />\n <RateRow label=\"Open rate\" rate={report.rates.open} />\n <RateRow label=\"Click rate\" rate={report.rates.click} />\n <RateRow\n label=\"Click-to-open rate\"\n rate={report.rates.clickToOpen}\n />\n <RateRow label=\"Bounce rate\" rate={report.rates.bounce} />\n <RateRow label=\"Complaint rate\" rate={report.rates.complaint} />\n <RateRow\n label=\"Unsubscribe rate\"\n rate={report.rates.unsubscribe}\n />\n </Stack>\n </Section>\n\n <Divider />\n\n <Section title=\"Who this went to\">\n {report.audiences.length ? (\n <>\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>{'Audience'}</TableCell>\n <TableCell align=\"right\">{'Emails'}</TableCell>\n <TableCell align=\"right\">{'Addressed'}</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {report.audiences.map((audience) => (\n <TableRow key={audience.id}>\n <TableCell>{audience.label}</TableCell>\n <TableCell align=\"right\">\n {audience.campaigns.toLocaleString()}\n </TableCell>\n <TableCell align=\"right\" sx={{ fontWeight: 'bold' }}>\n {audience.addressed.toLocaleString()}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </ScrollTable>\n {/*\n * The naming rule, stated rather than left to be discovered\n * from a list whose name no longer matches the one in the\n * audiences section.\n */}\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Each list is named as it was when the email was sent, so ' +\n 'renaming or deleting a list does not rewrite what a past ' +\n 'send went to.'}\n </Typography>\n </>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No email sent from this template has gone out yet.'}\n </Typography>\n )}\n </Section>\n\n {orderedMessages.length ? (\n <>\n <Divider />\n <Section title=\"Emails using this template\">\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>{'Subject'}</TableCell>\n <TableCell>{'State'}</TableCell>\n <TableCell>{'When'}</TableCell>\n <TableCell align=\"right\">{'Addressed'}</TableCell>\n <TableCell align=\"right\">{'Opens'}</TableCell>\n <TableCell align=\"right\">{'Clicks'}</TableCell>\n <TableCell align=\"right\" />\n </TableRow>\n </TableHead>\n <TableBody>\n {orderedMessages.map((message) => (\n <TableRow\n key={message.campaignId}\n hover\n onClick={() => router.push(messageHref(message))}\n sx={{ cursor: 'pointer' }}\n >\n <TableCell>\n {/*\n The row's own handler would fire too and push the\n same route twice — one history entry per back\n press.\n */}\n <AppLink\n href={messageHref(message)}\n onClick={(event: {\n stopPropagation: () => void\n }) => event.stopPropagation()}\n >\n {message.subject}\n </AppLink>\n </TableCell>\n <TableCell>\n {/*\n What the message is DOING. One delivering an\n audience larger than one batch is written back as\n `scheduled` between runs, so the stored status\n reads \"Scheduled\" about a send already in progress.\n */}\n <Chip\n size=\"small\"\n label={campaignSendDisplay(message as never).label}\n />\n </TableCell>\n <TableCell>\n {message.scheduledForMs\n ? new Date(message.scheduledForMs).toLocaleString()\n : '—'}\n </TableCell>\n <TableCell align=\"right\">\n {Number(\n message.stats?.recipients ?? 0,\n ).toLocaleString()}\n </TableCell>\n <TableCell align=\"right\">\n {Number(message.stats?.opens ?? 0).toLocaleString()}\n </TableCell>\n <TableCell align=\"right\">\n {Number(message.stats?.clicks ?? 0).toLocaleString()}\n </TableCell>\n <TableCell\n align=\"right\"\n sx={{ width: 56 }}\n onClick={(event) => event.stopPropagation()}\n >\n <RowActionsMenu\n label={String(message.subject)}\n items={messageActions(message)}\n />\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </ScrollTable>\n </Section>\n </>\n ) : null}\n </Stack>\n </CardDisplay>\n )\n}\nEmailTemplateReportCard.displayName = 'EmailTemplateReportCard'\n\nexport default EmailTemplateReportCard\n"],"names":["pluginDocsHelp","pluginRecordHref","mdiBullhornOutline","mdiEyeOutline","AppLink","CardDisplay","MdiIcon","Figure","RateRow","Section","RowActionsMenu","ScrollTable","useConsoleHostRoute","useFirestore","useFirestoreCollection","useOrgDataScope","Alert","Chip","Divider","Stack","TableBody","TableCell","TableHead","TableRow","Typography","collection","documentId","limit","orderBy","query","where","useRouter","useMemo","CAMPAIGN_SEND_CONTAINER_FIELD","campaignSendDisplay","campaignSendVisibleTo","orgCampaignSendsPath","emailSendTimeMs","templateReport","TEMPLATE_CAMPAIGN_CEILING","reportDocsHelp","anchor","excerpt","EmailTemplateReportCard","props","hostId","screenId","basePath","orgSlug","subdomain","campaignHref","campaignId","host","firestore","router","orgId","sendScope","data","messageDocs","idField","truncated","length","messages","slice","map","message","String","$id","subject","sentAtMs","status","audience","listId","listName","stats","scheduledForMs","emailCampaignId","orderedMessages","sort","a","b","report","messageHref","messageActions","key","label","icon","path","size","href","undefined","disabled","disabledReason","header","help","contentGutterX","contentGutterY","spacing","caveats","caveat","severity","id","title","direction","useFlexGap","sx","flexWrap","value","sentCampaigns","note","recipients","sent","delivered","bounced","complained","opens","uniqueOpens","clicks","uniqueClicks","unsubscribes","rate","rates","delivery","open","click","clickToOpen","bounce","complaint","unsubscribe","audiences","align","campaigns","toLocaleString","fontWeight","addressed","variant","color","hover","onClick","push","cursor","event","stopPropagation","Date","Number","width","items","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;AAEA,SAASA,cAAc,QAAQ,eAAc;AAC7C,SAASC,gBAAgB,QAAQ,mDAAkD;AACnF,SAASC,kBAAkB,EAAEC,aAAa,QAAQ,yBAAwB;AAC1E,SAASC,OAAO,EAAEC,WAAW,EAAEC,OAAO,QAAQ,uBAAsB;AACpE,SACEC,MAAM,EACNC,OAAO,EACPC,OAAO,QACF,6DAA4D;AACnE,OAAOC,oBAEA,6DAA4D;AACnE,SAASC,WAAW,QAAQ,yDAAwD;AACpF,SACEC,mBAAmB,EACnBC,YAAY,EACZC,sBAAsB,EACtBC,eAAe,QACV,iCAAgC;AACvC,SACEC,KAAK,EACLC,IAAI,EACJC,OAAO,EACPC,KAAK,EACLC,SAAS,EACTC,SAAS,EACTC,SAAS,EACTC,QAAQ,EACRC,UAAU,QACL,gBAAe;AACtB,SACEC,UAAU,EACVC,UAAU,EACVC,KAAK,EACLC,OAAO,EACPC,KAAK,EACLC,KAAK,QACA,qBAAoB;AAC3B,SAASC,SAAS,QAAQ,kBAAiB;AAC3C,SAASC,OAAO,QAAQ,QAAO;AAC/B,SACEC,6BAA6B,EAC7BC,mBAAmB,EACnBC,qBAAqB,EACrBC,oBAAoB,QACf,iCAA6B;AACpC,SAASC,eAAe,QAAQ,2BAAuB;AACvD,SAASC,cAAc,QAA+B,8BAA0B;AAEhF;;;;;;;CAOC,GACD,OAAO,MAAMC,4BAA4B,IAAG;AAE5C,MAAMC,iBAAiBxC,eAAe,kBAAkB;IACtDyC,QAAQ;IACRC,SACE,0EACA;AACJ;AAyBA;;;;;;;;;;;;;;CAcC,GACD,OAAO,SAASC,wBAAwBC,KAAmC;;IACzE,MAAM,EAAEC,MAAM,EAAEC,QAAQ,EAAEC,QAAQ,EAAE,GAAGH;IACvC,MAAM,EAAEI,OAAO,EAAEC,SAAS,EAAE,GAAGrC,oBAAoBiC;IACnD;;;;GAIC,GACD,MAAMK,eAAe,CAACC,aACpBH,WAAWC,YACPhD,iBAAiB,YAAY;YAAE+C;YAASI,MAAMH;QAAU,GAAGE,cAC3D;IACN,MAAME,YAAYxC;IAClB,MAAMyC,SAASvB;IAEf;;;;;;;;;;;;;;;;;;;;GAoBC,GACD,MAAM,EAAEwB,KAAK,EAAE,GAAGxC,gBAAgB;QAAE8B;IAAO;IAC3C,MAAMW,YAAYxB,QAAQ,IAAMG,sBAAsBU,SAAS;QAACA;KAAO;IACvE,MAAM,EAAEY,MAAMC,WAAW,EAAE,GAAG5C,uBAC5B,IACEyC,QACI1B,MACEJ,WAAW4B,cAAcjB,qBAAqBmB,SAC9CzB,MAAM,aAAa,sBAAsB0B,YACzC1B,MAAM,oBAAoB,MAAMgB,WAChClB,QAAQF,eACRC,MAAMY,4BAA4B,MAEpC,MACN;QAACc;QAAWE;QAAOC;QAAWV;KAAS,EACvC;QAAEa,SAAS;IAAM;IAGnB,MAAMC,YAAY,SAACF,+BAAAA,YAAaG,MAAM,mBAAI,KAAKtB;IAC/C,MAAMuB,WAAW9B,QACf,IACE,CAAC0B,sBAAAA,cAAe,EAAE,EACfK,KAAK,CAAC,GAAGxB,2BACTyB,GAAG,CAAC,CAACC;gBAUKA,iBAGMA,kBACEA;mBAdK;gBACtBd,YAAYe,OAAOD,QAAQE,GAAG;gBAC9BC,SAASF,OAAOD,QAAQG,OAAO,IAAI;gBACnC;;;;;WAKC,GACDC,UACEH,QAAOD,kBAAAA,QAAQK,MAAM,YAAdL,kBAAkB,QAAQ,SAC7B5B,gBAAgB4B,YAAY,OAC5B;gBACNK,QAAQJ,QAAOD,mBAAAA,QAAQK,MAAM,YAAdL,mBAAkB;gBACjCM,UAAUL,QAAOD,oBAAAA,QAAQM,QAAQ,YAAhBN,oBAAoB;eACjCA,QAAQO,MAAM,GAAG;gBAAEA,QAAQN,OAAOD,QAAQO,MAAM;YAAE,IAAI,CAAC,GACvDP,QAAQQ,QAAQ,GAAG;gBAAEA,UAAUP,OAAOD,QAAQQ,QAAQ;YAAE,IAAI,CAAC;gBACjEC,OAAOT,QAAQS,KAAK;gBACpBC,gBAAgBtC,gBAAgB4B;eAS5BA,OAAO,CAAChC,8BAA8B,GACtC;gBACE2C,iBAAiBV,OACfD,OAAO,CAAChC,8BAA8B;YAE1C,IACA,CAAC;YAEX;QAACyB;KAAY;IAEf,MAAMmB,kBAAkB7C,QACtB,IAAM;eAAI8B;SAAS,CAACgB,IAAI,CAAC,CAACC,GAAGC,IAAMA,EAAEL,cAAc,GAAGI,EAAEJ,cAAc,GACtE;QAACb;KAAS;IAEZ,MAAMmB,SAASjD,QACb,IAAMM,eAAewB,UAAUF,YAC/B;QAACE;QAAUF;KAAU;IAGvB,MAAMsB,cAAc,CAACjB,UACnB,GAAGlB,SAAS,UAAU,EAAEkB,QAAQd,UAAU,EAAE;IAE9C;;;;;;;GAOC,GACD,MAAMgC,iBAAiB,CAAClB,UAAmD;YACzE;gBACEmB,KAAK;gBACLC,OAAO;gBACPC,oBAAM,KAAChF;oBAAQiF,MAAMpF,cAAcoF,IAAI;oBAAEC,MAAM;;gBAC/CC,MAAMP,YAAYjB;YACpB;YACA;gBACEmB,KAAK;gBACLC,OAAO;gBACPC,oBAAM,KAAChF;oBAAQiF,MAAMrF,mBAAmBqF,IAAI;oBAAEC,MAAM;;gBACpD,mEAAmE;gBACnEC,MACE,AAACxB,QAAQW,eAAe,IAAI1B,aAAae,QAAQW,eAAe,KAChEc;gBACFC,UACE,CAAC1B,QAAQW,eAAe,IAAI,CAAC1B,aAAae,QAAQW,eAAe;gBACnEgB,gBAAgB3B,QAAQW,eAAe,GACnC,iDACA;YACN;SACD;IAED,qBACE,KAACvE;QACCwF,QAAQ;QACRC,MAAMtD;QACNuD,cAAc;QACdC,cAAc;kBAEd,cAAA,MAAC7E;YAAM8E,SAAS;;gBACbhB,OAAOiB,OAAO,CAAClC,GAAG,CAAC,CAACmC,uBACnB,KAACnF;wBAAsBoF,UAAS;kCAC7BD,OAAOlC,OAAO;uBADLkC,OAAOE,EAAE;8BAavB,KAAC5F;oBAAQ6F,OAAM;8BACb,cAAA,MAACnF;wBACCoF,WAAU;wBACVN,SAAS;wBACTO,UAAU;wBACVC,IAAI;4BAAEC,UAAU;wBAAO;;0CAEvB,KAACnG;gCACC8E,OAAM;gCACNsB,OAAO1B,OAAO2B,aAAa;gCAC3BC,MAAK;;0CAEP,KAACtG;gCACC8E,OAAM;gCACNsB,OAAO1B,OAAO6B,UAAU;gCACxBD,MAAK;;0CAEP,KAACtG;gCACC8E,OAAM;gCACNsB,OAAO1B,OAAO8B,IAAI;gCAClBF,MAAK;;0CAEP,KAACtG;gCACC8E,OAAM;gCACNsB,OAAO1B,OAAO+B,SAAS;gCACvBH,MAAK;;0CAEP,KAACtG;gCAAO8E,OAAM;gCAAUsB,OAAO1B,OAAOgC,OAAO;gCAAEJ,MAAK;;0CACpD,KAACtG;gCACC8E,OAAM;gCACNsB,OAAO1B,OAAOiC,UAAU;gCACxBL,MAAK;;;;;8BAKX,KAAC3F;8BAED,KAACT;oBAAQ6F,OAAM;8BACb,cAAA,MAACnF;wBACCoF,WAAU;wBACVN,SAAS;wBACTO,UAAU;wBACVC,IAAI;4BAAEC,UAAU;wBAAO;;0CAEvB,KAACnG;gCACC8E,OAAM;gCACNsB,OAAO1B,OAAOkC,KAAK;gCACnBN,MAAK;;0CAEP,KAACtG;gCACC8E,OAAM;gCACNsB,OAAO1B,OAAOmC,WAAW;gCACzBP,MAAK;;0CAEP,KAACtG;gCACC8E,OAAM;gCACNsB,OAAO1B,OAAOoC,MAAM;gCACpBR,MAAK;;0CAEP,KAACtG;gCACC8E,OAAM;gCACNsB,OAAO1B,OAAOqC,YAAY;gCAC1BT,MAAK;;0CAEP,KAACtG;gCACC8E,OAAM;gCACNsB,OAAO1B,OAAOsC,YAAY;gCAC1BV,MAAK;;;;;8BAKX,KAAC3F;8BAED,KAACT;oBAAQ6F,OAAM;8BACb,cAAA,MAACnF;wBAAM8E,SAAS;;0CACd,KAACzF;gCAAQ6E,OAAM;gCAAgBmC,MAAMvC,OAAOwC,KAAK,CAACC,QAAQ;;0CAC1D,KAAClH;gCAAQ6E,OAAM;gCAAYmC,MAAMvC,OAAOwC,KAAK,CAACE,IAAI;;0CAClD,KAACnH;gCAAQ6E,OAAM;gCAAamC,MAAMvC,OAAOwC,KAAK,CAACG,KAAK;;0CACpD,KAACpH;gCACC6E,OAAM;gCACNmC,MAAMvC,OAAOwC,KAAK,CAACI,WAAW;;0CAEhC,KAACrH;gCAAQ6E,OAAM;gCAAcmC,MAAMvC,OAAOwC,KAAK,CAACK,MAAM;;0CACtD,KAACtH;gCAAQ6E,OAAM;gCAAiBmC,MAAMvC,OAAOwC,KAAK,CAACM,SAAS;;0CAC5D,KAACvH;gCACC6E,OAAM;gCACNmC,MAAMvC,OAAOwC,KAAK,CAACO,WAAW;;;;;8BAKpC,KAAC9G;8BAED,KAACT;oBAAQ6F,OAAM;8BACZrB,OAAOgD,SAAS,CAACpE,MAAM,iBACtB;;0CACE,MAAClD;gCAAY6E,MAAK;;kDAChB,KAAClE;kDACC,cAAA,MAACC;;8DACC,KAACF;8DAAW;;8DACZ,KAACA;oDAAU6G,OAAM;8DAAS;;8DAC1B,KAAC7G;oDAAU6G,OAAM;8DAAS;;;;;kDAG9B,KAAC9G;kDACE6D,OAAOgD,SAAS,CAACjE,GAAG,CAAC,CAACO,yBACrB,MAAChD;;kEACC,KAACF;kEAAWkD,SAASc,KAAK;;kEAC1B,KAAChE;wDAAU6G,OAAM;kEACd3D,SAAS4D,SAAS,CAACC,cAAc;;kEAEpC,KAAC/G;wDAAU6G,OAAM;wDAAQzB,IAAI;4DAAE4B,YAAY;wDAAO;kEAC/C9D,SAAS+D,SAAS,CAACF,cAAc;;;+CANvB7D,SAAS8B,EAAE;;;;0CAiBhC,KAAC7E;gCAAW+G,SAAQ;gCAAUC,OAAM;0CACjC,8DACC,8DACA;;;uCAIN,KAAChH;wBAAW+G,SAAQ;wBAAQC,OAAM;kCAC/B;;;gBAKN3D,gBAAgBhB,MAAM,iBACrB;;sCACE,KAAC3C;sCACD,KAACT;4BAAQ6F,OAAM;sCACb,cAAA,MAAC3F;gCAAY6E,MAAK;;kDAChB,KAAClE;kDACC,cAAA,MAACC;;8DACC,KAACF;8DAAW;;8DACZ,KAACA;8DAAW;;8DACZ,KAACA;8DAAW;;8DACZ,KAACA;oDAAU6G,OAAM;8DAAS;;8DAC1B,KAAC7G;oDAAU6G,OAAM;8DAAS;;8DAC1B,KAAC7G;oDAAU6G,OAAM;8DAAS;;8DAC1B,KAAC7G;oDAAU6G,OAAM;;;;;kDAGrB,KAAC9G;kDACEyD,gBAAgBb,GAAG,CAAC,CAACC;;gDAyCdA,gBAIMA,iBAGAA;iEA/CZ,MAAC1C;gDAECkH,KAAK;gDACLC,SAAS,IAAMpF,OAAOqF,IAAI,CAACzD,YAAYjB;gDACvCwC,IAAI;oDAAEmC,QAAQ;gDAAU;;kEAExB,KAACvH;kEAMC,cAAA,KAACjB;4DACCqF,MAAMP,YAAYjB;4DAClByE,SAAS,CAACG,QAEJA,MAAMC,eAAe;sEAE1B7E,QAAQG,OAAO;;;kEAGpB,KAAC/C;kEAOC,cAAA,KAACJ;4DACCuE,MAAK;4DACLH,OAAOnD,oBAAoB+B,SAAkBoB,KAAK;;;kEAGtD,KAAChE;kEACE4C,QAAQU,cAAc,GACnB,IAAIoE,KAAK9E,QAAQU,cAAc,EAAEyD,cAAc,KAC/C;;kEAEN,KAAC/G;wDAAU6G,OAAM;kEACdc,gBACC/E,iBAAAA,QAAQS,KAAK,qBAAbT,eAAe6C,UAAU,mBAAI,GAC7BsB,cAAc;;kEAElB,KAAC/G;wDAAU6G,OAAM;kEACdc,iBAAO/E,kBAAAA,QAAQS,KAAK,qBAAbT,gBAAekD,KAAK,oBAAI,GAAGiB,cAAc;;kEAEnD,KAAC/G;wDAAU6G,OAAM;kEACdc,iBAAO/E,kBAAAA,QAAQS,KAAK,qBAAbT,gBAAeoD,MAAM,oBAAI,GAAGe,cAAc;;kEAEpD,KAAC/G;wDACC6G,OAAM;wDACNzB,IAAI;4DAAEwC,OAAO;wDAAG;wDAChBP,SAAS,CAACG,QAAUA,MAAMC,eAAe;kEAEzC,cAAA,KAACpI;4DACC2E,OAAOnB,OAAOD,QAAQG,OAAO;4DAC7B8E,OAAO/D,eAAelB;;;;+CAvDrBA,QAAQd,UAAU;;;;;;;qBAgEjC;;;;AAIZ;AACAR,wBAAwBwG,WAAW,GAAG;AAEtC,eAAexG,wBAAuB"}
@@ -36,8 +36,8 @@ import { DUPLICATE_MENU_LABEL, useDuplicateResource, useFirestore, useFirestoreC
36
36
  import { Button, Chip, Stack, Typography } from "@mui/material";
37
37
  import { useRouter } from "next/navigation";
38
38
  import { useCallback, useMemo, useState } from "react";
39
- import { campaignPlacedOnHost, campaignSendDisplay, CAMPAIGN_SEND_CONTAINER_FIELD } from "@aglyn/shared-ui-email-campaigns/model/campaign-container";
40
- import { emailListTimeMs, emailSendTimeMs } from "@aglyn/shared-ui-email-campaigns/model/email-record";
39
+ import { campaignPlacedOnHost, campaignSendDisplay, CAMPAIGN_SEND_CONTAINER_FIELD } from "../model/campaign-container.js";
40
+ import { emailListTimeMs, emailSendTimeMs } from "../model/email-record.js";
41
41
  import { useCampaignManageApi, useCampaignSendApi } from "./use-campaign-send-api.js";
42
42
  import { CAMPAIGN_SEND_STATUS_OPTIONS, SINGLE_SEND_FILTER_VALUE, campaignEmailsListQuery, campaignSendsScope, emailCampaignQueryClauses } from "../model/campaign-list-query.js";
43
43
  import { useMarketingHubPath } from "./use-marketing-hub-path.js";