@nivaro/react 0.3.40 → 0.3.42

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 (99) hide show
  1. package/dist/components/LayoutForm.js +100 -98
  2. package/dist/full.css +1 -1
  3. package/dist/hooks/useSessionRecorder.js +1 -1
  4. package/dist/index.d.ts +645 -16
  5. package/dist/index.js +814 -775
  6. package/dist/shared/components/ActivityEntry.js +154 -0
  7. package/dist/shared/components/AudienceConfirmPanel.js +98 -0
  8. package/dist/shared/components/BackgroundRunOffer.js +43 -31
  9. package/dist/shared/components/BroadcastView.js +225 -204
  10. package/dist/shared/components/CollectionBrowserView.js +3924 -3584
  11. package/dist/shared/components/ConfigHistory.js +153 -0
  12. package/dist/shared/components/ConfigReadBack.js +65 -0
  13. package/dist/shared/components/DataTable.js +379 -346
  14. package/dist/shared/components/ForceReloadBanner.js +47 -40
  15. package/dist/shared/components/IdleReloadWatcher.js +69 -0
  16. package/dist/shared/components/ItemEditForm.js +4041 -3853
  17. package/dist/shared/components/MyWorkView.js +278 -213
  18. package/dist/shared/components/NotificationBell.js +130 -121
  19. package/dist/shared/components/PickerExcludedRows.js +3 -3
  20. package/dist/shared/components/ProfileView.js +776 -1003
  21. package/dist/shared/components/RecentRecordsRail.js +34 -16
  22. package/dist/shared/components/RecordReadView.js +500 -477
  23. package/dist/shared/components/Skeletons.js +199 -0
  24. package/dist/shared/components/SlowNotice.js +63 -0
  25. package/dist/shared/components/Term.js +23 -0
  26. package/dist/shared/components/WidgetSlot.js +350 -353
  27. package/dist/shared/components/access/PermissionSummary.js +106 -0
  28. package/dist/shared/components/bulk/BulkActionButtons.js +76 -73
  29. package/dist/shared/components/chat/ChatPanel.js +902 -886
  30. package/dist/shared/components/chat/MemberPaging.js +70 -0
  31. package/dist/shared/components/chat/RoomParts.js +147 -117
  32. package/dist/shared/components/chat/ScreenClip.js +73 -69
  33. package/dist/shared/components/chat/chat-core.js +449 -396
  34. package/dist/shared/components/chat/huddle/huddle-store.js +48 -43
  35. package/dist/shared/components/help-videos/api.js +2 -5
  36. package/dist/shared/components/help-videos/viewer/HelpVideoMatchChip.js +79 -0
  37. package/dist/shared/components/help-videos/viewer/matchChip.js +50 -0
  38. package/dist/shared/components/imports/CollectionImportPanel.js +139 -133
  39. package/dist/shared/components/imports/RunDetailSheet.js +222 -227
  40. package/dist/shared/components/integrations/console/ErrorExplanation.js +121 -0
  41. package/dist/shared/components/integrations/console/PartnerDetail.js +175 -174
  42. package/dist/shared/components/integrations/console/SubmissionRow.js +36 -34
  43. package/dist/shared/components/integrations/console/drill/SubmissionDrill.js +50 -48
  44. package/dist/shared/components/item-edit/FieldComments.js +212 -0
  45. package/dist/shared/components/item-edit/FieldRow.js +340 -336
  46. package/dist/shared/components/item-edit/RecordGrantTools.js +28 -28
  47. package/dist/shared/components/item-edit/RecordHeaderActions.js +23 -22
  48. package/dist/shared/components/item-edit/RelationCombobox.js +231 -222
  49. package/dist/shared/components/item-edit/RuleHiddenNotes.js +55 -0
  50. package/dist/shared/components/item-edit/field-comments-context.js +6 -0
  51. package/dist/shared/components/item-edit/field-sources.js +33 -0
  52. package/dist/shared/components/item-edit/rule-hidden.js +30 -0
  53. package/dist/shared/components/layout-editor/HistoryPanel.js +113 -96
  54. package/dist/shared/components/layout-editor/editors/cascade-filters.js +170 -145
  55. package/dist/shared/components/layout-editor/inspector/Breadcrumb.js +22 -18
  56. package/dist/shared/components/layout-editor/inspector/GroupInspector.js +177 -159
  57. package/dist/shared/components/layout-editor/inspector/PickerTab.js +35 -34
  58. package/dist/shared/components/layout-editor/inspector/RulesTab.js +124 -111
  59. package/dist/shared/components/layout-editor/inspector/kit.js +292 -277
  60. package/dist/shared/components/layout-editor/parity-keys.json.js +1 -1
  61. package/dist/shared/components/layout-editor/settings/settings-registry.js +1 -1
  62. package/dist/shared/components/list/CopyListLinkButton.js +41 -0
  63. package/dist/shared/components/list/InlineEdit.js +363 -0
  64. package/dist/shared/components/notifications/NotificationCenterView.js +213 -181
  65. package/dist/shared/components/notifications/NotificationDetailBits.js +205 -82
  66. package/dist/shared/components/notifications/NotificationRulesEditor.js +515 -0
  67. package/dist/shared/components/notifications/TestMyRules.js +137 -0
  68. package/dist/shared/components/notifications/changes-lines.js +24 -0
  69. package/dist/shared/components/notifications/why-lines.js +64 -0
  70. package/dist/shared/components/panels/CommentPanel.js +316 -281
  71. package/dist/shared/components/panels/LegalHoldBanner.js +38 -0
  72. package/dist/shared/components/panels/PipelinePanel.js +201 -196
  73. package/dist/shared/components/panels/RevisionsPanel.js +496 -479
  74. package/dist/shared/components/pipeline/PipelineEditorView.js +1587 -1560
  75. package/dist/shared/components/pipeline/PipelineStateOwners.js +116 -103
  76. package/dist/shared/components/profile/PersonDidPanel.js +188 -0
  77. package/dist/shared/components/profile/PersonProfile.js +57 -52
  78. package/dist/shared/components/queue/QueueWorklist.js +1526 -1365
  79. package/dist/shared/hooks/useSlowReason.js +39 -0
  80. package/dist/shared/lib/activity-sentence.js +670 -0
  81. package/dist/shared/lib/config-sentences.js +418 -0
  82. package/dist/shared/lib/core-glossary.js +229 -0
  83. package/dist/shared/lib/error-hints.js +159 -0
  84. package/dist/shared/lib/error-message.js +105 -0
  85. package/dist/shared/lib/error-toast.js +22 -0
  86. package/dist/shared/lib/field-comments.js +25 -0
  87. package/dist/shared/lib/filter-sentence.js +261 -0
  88. package/dist/shared/lib/glossary.js +55 -48
  89. package/dist/shared/lib/idle-reload.js +138 -0
  90. package/dist/shared/lib/layout-resolve.js +42 -29
  91. package/dist/shared/lib/list-inline-edit.js +190 -0
  92. package/dist/shared/lib/list-link.js +147 -0
  93. package/dist/shared/lib/prefetch.js +259 -106
  94. package/dist/shared/lib/record-ref.js +72 -0
  95. package/dist/shared/lib/reload-holds.js +25 -0
  96. package/dist/shared/lib/safe-error.js +52 -24
  97. package/dist/shared/lib/skeleton-shapes.js +72 -0
  98. package/dist/shared/lib/slow-reason.js +63 -0
  99. package/package.json +1 -1
@@ -0,0 +1,515 @@
1
+ import { jsxs as r, jsx as t } from "react/jsx-runtime";
2
+ import { RotateCcw as U } from "lucide-react";
3
+ import { useState as B } from "react";
4
+ import { cn as M } from "../../lib/utils.js";
5
+ import { SimpleSelectXs as F } from "../ui/SimpleSelect.js";
6
+ const Q = [
7
+ {
8
+ key: "mentions",
9
+ label: "Mentions",
10
+ help: "Someone @-mentions you in a comment, note, or chat room."
11
+ },
12
+ {
13
+ key: "workflow",
14
+ label: "Workflow & approvals",
15
+ help: "A record you own or follow moves state, needs your approval, is sent back, or an approval chain reaches your step. Also support requests you work on the desk (new, assigned, replied)."
16
+ },
17
+ {
18
+ key: "sla",
19
+ label: "SLA & escalations",
20
+ help: "A record you own is nearing or past its SLA, and escalation-ladder notices as they climb. SLA escalations always email immediately."
21
+ },
22
+ {
23
+ key: "watch",
24
+ label: "Watches",
25
+ help: "Something you explicitly watch fired: field watches, per-record subscriptions, and watched stock parts (Stock Planning)."
26
+ },
27
+ {
28
+ key: "alerts",
29
+ label: "Alerts",
30
+ help: "Threshold alerts you subscribe to: metric alert rules, per-record alert definitions, report alerts, and the alert digest."
31
+ },
32
+ {
33
+ key: "audit",
34
+ label: "Audit log patterns",
35
+ help: "An activity-pattern alert you subscribe to matched: someone made the kind of change it watches for (for example a large amount edited, or a zone changed). Past the rule’s hourly limit the rest arrive as one digest."
36
+ },
37
+ {
38
+ key: "anomaly",
39
+ label: "Anomaly detections",
40
+ help: "Anomaly rules you created flag an outlier, spike, or duplicate pattern."
41
+ },
42
+ {
43
+ key: "reports",
44
+ label: "Reports",
45
+ help: "Scheduled report emails, Report Studio subscription digests, and saved-view change digests."
46
+ },
47
+ {
48
+ key: "integrations",
49
+ label: "Integrations",
50
+ help: "A partner system was not told something about your record, a send is still unanswered, or an integration alert you turned on in the Integrations console fired."
51
+ },
52
+ {
53
+ key: "huddle",
54
+ label: "Huddles & calls",
55
+ help: "Someone calls you in a huddle or adds you to one, and reminders for huddles you were invited to. Push is what rings a closed tab."
56
+ },
57
+ {
58
+ key: "chaos",
59
+ label: "Chaos drills",
60
+ help: "Chaos Monkey drills starting and ending, and pushes about drills you started or take part in (with a Stop button). Only administrators and chaos operators get these."
61
+ },
62
+ {
63
+ key: "system",
64
+ label: "System & digests",
65
+ help: "Maintenance windows, failing monitors, import results, updates on support requests you raised, and the daily action summary itself. Maintenance and monitor failures always email immediately."
66
+ },
67
+ {
68
+ key: "other",
69
+ label: "Everything else",
70
+ help: "Any notification that does not fit a category above — direct messages from other users, access requests, task assignments."
71
+ }
72
+ ], ee = ["inapp", "push", "teams", "email", "quiet_override"], D = ["push_after_min", "email_after_min"], S = (a) => !!a && (a.quiet_start !== void 0 || a.quiet_end !== void 0);
73
+ function te(a, l) {
74
+ var b, y, p, w, _, T, C, $;
75
+ const d = {}, m = S(a) ? a : S(l) ? l : null;
76
+ (m == null ? void 0 : m.quiet_start) !== void 0 && (d.quiet_start = m.quiet_start), (m == null ? void 0 : m.quiet_end) !== void 0 && (d.quiet_end = m.quiet_end);
77
+ const R = {};
78
+ for (const f of /* @__PURE__ */ new Set([
79
+ ...Object.keys((l == null ? void 0 : l.matrix) ?? {}),
80
+ ...Object.keys((a == null ? void 0 : a.matrix) ?? {})
81
+ ])) {
82
+ const x = {};
83
+ for (const h of ee) {
84
+ const v = ((y = (b = a == null ? void 0 : a.matrix) == null ? void 0 : b[f]) == null ? void 0 : y[h]) ?? ((w = (p = l == null ? void 0 : l.matrix) == null ? void 0 : p[f]) == null ? void 0 : w[h]);
85
+ v !== void 0 && (x[h] = v);
86
+ }
87
+ R[f] = x;
88
+ }
89
+ d.matrix = R;
90
+ const A = {};
91
+ for (const f of /* @__PURE__ */ new Set([
92
+ ...Object.keys((l == null ? void 0 : l.escalation) ?? {}),
93
+ ...Object.keys((a == null ? void 0 : a.escalation) ?? {})
94
+ ])) {
95
+ const x = {};
96
+ for (const h of D) {
97
+ const v = ((T = (_ = a == null ? void 0 : a.escalation) == null ? void 0 : _[f]) == null ? void 0 : T[h]) ?? (($ = (C = l == null ? void 0 : l.escalation) == null ? void 0 : C[f]) == null ? void 0 : $[h]);
98
+ v !== void 0 && (x[h] = v);
99
+ }
100
+ A[f] = x;
101
+ }
102
+ return d.escalation = A, d;
103
+ }
104
+ function Y(a, l, d) {
105
+ return d(a) !== void 0 ? "person" : d(l) !== void 0 ? "role" : "system";
106
+ }
107
+ function N({ text: a }) {
108
+ return /* @__PURE__ */ t(
109
+ "span",
110
+ {
111
+ className: "ml-1 inline-flex h-3.5 w-3.5 cursor-help items-center justify-center rounded-full border border-slate-300 text-[9px] font-semibold normal-case tracking-normal text-slate-400 dark:border-border",
112
+ role: "img",
113
+ "data-tip": a,
114
+ "aria-label": a,
115
+ children: "?"
116
+ }
117
+ );
118
+ }
119
+ function q({ show: a, roleName: l }) {
120
+ const d = `From the ${l ? `"${l}" ` : ""}role default — change it to make it yours`;
121
+ return /* @__PURE__ */ t(
122
+ "span",
123
+ {
124
+ "data-from-role": a ? "" : void 0,
125
+ "aria-hidden": !a,
126
+ "data-tip": a ? d : void 0,
127
+ className: M(
128
+ "ml-0.5 inline-block rounded bg-violet-50 px-0.5 align-middle text-[8.5px] font-bold uppercase leading-[12px] text-violet-600 dark:bg-violet-500/15 dark:text-violet-300",
129
+ !a && "invisible"
130
+ ),
131
+ children: "R"
132
+ }
133
+ );
134
+ }
135
+ function ne({
136
+ value: a,
137
+ inherited: l,
138
+ inheritedName: d,
139
+ legacyEmail: m = "instant",
140
+ onCommit: R,
141
+ onLegacyEmail: A,
142
+ saving: b,
143
+ subject: y,
144
+ teams: p,
145
+ extraColumn: w
146
+ }) {
147
+ const _ = te(a, l), [T, C] = B(null), [$, f] = B(null), x = T ?? _.quiet_start ?? "", h = $ ?? _.quiet_end ?? "", v = S(a) ? "person" : S(l) ? "role" : "system", k = y === "person" && !!l, O = (e) => {
148
+ const o = {
149
+ ...a,
150
+ matrix: { ...a.matrix ?? {} },
151
+ escalation: { ...a.escalation ?? {} }
152
+ };
153
+ e(o), R(o);
154
+ }, P = (e, o, s) => O((i) => {
155
+ var c;
156
+ const n = { ...((c = i.matrix) == null ? void 0 : c[e]) ?? {} };
157
+ s === void 0 ? delete n[o] : n[o] = s;
158
+ const u = i.matrix ?? {};
159
+ Object.keys(n).length ? u[e] = n : delete u[e], i.matrix = u;
160
+ }), H = (e) => {
161
+ var o;
162
+ return ((o = l == null ? void 0 : l.matrix) == null ? void 0 : o[e]) ?? {};
163
+ }, W = (e) => {
164
+ var o, s, i, n;
165
+ return ((s = (o = a.matrix) == null ? void 0 : o[e]) == null ? void 0 : s.email) ?? (m === "daily" ? "daily" : ((n = (i = l == null ? void 0 : l.matrix) == null ? void 0 : i[e]) == null ? void 0 : n.email) ?? m);
166
+ }, X = (e) => {
167
+ var o, s, i, n;
168
+ return ((s = (o = a.matrix) == null ? void 0 : o[e]) == null ? void 0 : s.email) !== void 0 || m === "daily" ? "person" : ((n = (i = l == null ? void 0 : l.matrix) == null ? void 0 : i[e]) == null ? void 0 : n.email) !== void 0 ? "role" : "system";
169
+ }, I = (e, o) => O((s) => {
170
+ e === void 0 && o === void 0 ? (delete s.quiet_start, delete s.quiet_end) : (s.quiet_start = e ?? "", s.quiet_end = o ?? "");
171
+ }), z = (e, o, s) => O((i) => {
172
+ var c, g, E;
173
+ const n = { ...((c = i.escalation) == null ? void 0 : c[e]) ?? {} };
174
+ s ? n[o] = s : (E = (g = l == null ? void 0 : l.escalation) == null ? void 0 : g[e]) != null && E[o] ? n[o] = 0 : delete n[o];
175
+ const u = i.escalation ?? {};
176
+ Object.keys(n).length ? u[e] = n : delete u[e], i.escalation = u;
177
+ }), K = (e) => O((o) => {
178
+ var s;
179
+ (s = o.matrix) == null || delete s[e];
180
+ }), V = (e) => O((o) => {
181
+ var s;
182
+ (s = o.escalation) == null || delete s[e];
183
+ }), G = S(a) || Object.keys(a.matrix ?? {}).length > 0 || Object.keys(a.escalation ?? {}).length > 0, J = [
184
+ { value: "", label: "never" },
185
+ { value: "15", label: "15 min" },
186
+ { value: "60", label: "1 hour" },
187
+ { value: "240", label: "4 hours" },
188
+ { value: "480", label: "8 hours" },
189
+ { value: "1440", label: "1 day" }
190
+ ], L = [
191
+ { value: "instant", label: "Individual email" },
192
+ { value: "daily", label: "Daily summary" },
193
+ { value: "off", label: "No email" }
194
+ ], Z = y === "role" ? "members" : "you";
195
+ return /* @__PURE__ */ r("div", { className: "space-y-3", "data-notification-rules-editor": y, children: [
196
+ k && /* @__PURE__ */ r(
197
+ "div",
198
+ {
199
+ "data-role-defaults-note": !0,
200
+ className: "flex flex-wrap items-center gap-2 rounded-md border border-violet-200 bg-violet-50/60 px-3 py-1.5 text-[11.5px] text-violet-900 dark:border-violet-500/30 dark:bg-violet-500/10 dark:text-violet-200",
201
+ children: [
202
+ /* @__PURE__ */ r("span", { className: "min-w-0 flex-1", children: [
203
+ "Your role",
204
+ d ? ` (${d})` : "",
205
+ " sets starting rules. Values marked",
206
+ " ",
207
+ /* @__PURE__ */ t(q, { show: !0, roleName: d }),
208
+ " come from it until you change them."
209
+ ] }),
210
+ G && /* @__PURE__ */ t(
211
+ "button",
212
+ {
213
+ type: "button",
214
+ disabled: b,
215
+ onClick: () => R(
216
+ a.teams_muted_until ? { teams_muted_until: a.teams_muted_until } : {}
217
+ ),
218
+ className: "underline decoration-dotted underline-offset-2 disabled:opacity-50",
219
+ children: "Use the role defaults for everything"
220
+ }
221
+ )
222
+ ]
223
+ }
224
+ ),
225
+ /* @__PURE__ */ r("div", { className: "flex flex-wrap items-center gap-2 text-[12.5px] text-slate-600 dark:text-muted-foreground", children: [
226
+ "Quiet hours (ET):",
227
+ /* @__PURE__ */ t(
228
+ "input",
229
+ {
230
+ type: "time",
231
+ value: x,
232
+ onChange: (e) => C(e.target.value),
233
+ onBlur: () => {
234
+ T != null && I(x || "", h || "");
235
+ },
236
+ "aria-label": "Quiet hours start",
237
+ className: "h-7 rounded-md border border-slate-200 bg-background px-1.5 text-[12px] dark:border-border"
238
+ }
239
+ ),
240
+ /* @__PURE__ */ t("span", { children: "to" }),
241
+ /* @__PURE__ */ t(
242
+ "input",
243
+ {
244
+ type: "time",
245
+ value: h,
246
+ onChange: (e) => f(e.target.value),
247
+ onBlur: () => {
248
+ $ != null && I(x || "", h || "");
249
+ },
250
+ "aria-label": "Quiet hours end",
251
+ className: "h-7 rounded-md border border-slate-200 bg-background px-1.5 text-[12px] dark:border-border"
252
+ }
253
+ ),
254
+ k && v === "role" && /* @__PURE__ */ t(q, { show: !0, roleName: d }),
255
+ (x || h) && /* @__PURE__ */ t(
256
+ "button",
257
+ {
258
+ type: "button",
259
+ onClick: () => {
260
+ C(""), f(""), S(l) ? I("", "") : I(void 0, void 0);
261
+ },
262
+ className: "text-[11.5px] text-slate-400 underline decoration-dotted underline-offset-2",
263
+ children: "Clear"
264
+ }
265
+ ),
266
+ k && v === "person" && S(l) && /* @__PURE__ */ r(
267
+ "button",
268
+ {
269
+ type: "button",
270
+ onClick: () => {
271
+ C(null), f(null), I(void 0, void 0);
272
+ },
273
+ className: "text-[11.5px] text-slate-400 underline decoration-dotted underline-offset-2",
274
+ children: [
275
+ "Use the role's (",
276
+ (l == null ? void 0 : l.quiet_start) || "—",
277
+ "–",
278
+ (l == null ? void 0 : l.quiet_end) || "—",
279
+ ")"
280
+ ]
281
+ }
282
+ )
283
+ ] }),
284
+ /* @__PURE__ */ r("table", { className: "w-full text-[12px]", children: [
285
+ /* @__PURE__ */ t("thead", { children: /* @__PURE__ */ r("tr", { className: "text-left text-[10.5px] uppercase tracking-wide text-slate-400", children: [
286
+ /* @__PURE__ */ t("th", { className: "py-1 font-semibold", children: "Category" }),
287
+ /* @__PURE__ */ r("th", { className: "py-1 text-center font-semibold", children: [
288
+ "In-app",
289
+ /* @__PURE__ */ t(N, { text: "The notification itself: the bell badge and the Notifications inbox in the app. Off = no row is written for this category, so nothing shows anywhere in the app." })
290
+ ] }),
291
+ /* @__PURE__ */ r("th", { className: "py-1 text-center font-semibold", children: [
292
+ "Push",
293
+ /* @__PURE__ */ t(N, { text: "A browser / device alert (the popup you get even with the tab closed) on devices where notifications are allowed. Off keeps the in-app row but stops the device alert. Quiet hours pause pushes too." })
294
+ ] }),
295
+ /* @__PURE__ */ r("th", { className: "py-1 text-center font-semibold", children: [
296
+ "Email",
297
+ /* @__PURE__ */ t(N, { text: "Individual email = one message as each event happens. Daily summary = held and delivered once a day in the action summary. No email = never emailed for this category (in-app and push are unaffected)." })
298
+ ] }),
299
+ (p == null ? void 0 : p.available) && /* @__PURE__ */ r("th", { className: "py-1 text-center font-semibold", "data-teams-column": "", children: [
300
+ "Teams",
301
+ /* @__PURE__ */ t(N, { text: "A message from the Nivaro bot in Microsoft Teams. Off by default: connect Teams, then turn on the categories wanted there." }),
302
+ p.header
303
+ ] }),
304
+ w && /* @__PURE__ */ t("th", { className: "py-1 text-center font-semibold", children: w.header }),
305
+ /* @__PURE__ */ r("th", { className: "py-1 text-center font-semibold", children: [
306
+ "Quiet hours",
307
+ /* @__PURE__ */ t(
308
+ N,
309
+ {
310
+ text: "Ticked = this category still pushes and emails during quiet hours (the in-app row always lands either way). Critical alerts always get through."
311
+ }
312
+ )
313
+ ] }),
314
+ k && /* @__PURE__ */ t("th", { className: "w-6 py-1", "aria-label": "Reset" })
315
+ ] }) }),
316
+ /* @__PURE__ */ t("tbody", { children: Q.map((e) => {
317
+ var n, u;
318
+ const o = ((n = _.matrix) == null ? void 0 : n[e.key]) ?? {}, s = (c) => k && Y(a, l, (g) => {
319
+ var E, j;
320
+ return (j = (E = g == null ? void 0 : g.matrix) == null ? void 0 : E[e.key]) == null ? void 0 : j[c];
321
+ }) === "role", i = (u = a.matrix) == null ? void 0 : u[e.key];
322
+ return /* @__PURE__ */ r("tr", { className: "border-t border-slate-100 dark:border-border", children: [
323
+ /* @__PURE__ */ r("td", { className: "py-1.5 text-slate-700 dark:text-foreground", children: [
324
+ e.label,
325
+ /* @__PURE__ */ t(N, { text: e.help })
326
+ ] }),
327
+ ["inapp", "push"].map((c) => /* @__PURE__ */ r("td", { className: "py-1.5 text-center", children: [
328
+ /* @__PURE__ */ t(
329
+ "input",
330
+ {
331
+ type: "checkbox",
332
+ checked: o[c] !== !1,
333
+ onChange: () => P(e.key, c, o[c] === !1),
334
+ disabled: b,
335
+ "aria-label": `${c === "inapp" ? "In-app" : "Push"} for ${e.label}`,
336
+ "data-notify-channel": `${e.key}:${c}`,
337
+ className: "h-3.5 w-3.5 align-middle"
338
+ }
339
+ ),
340
+ /* @__PURE__ */ t(q, { show: s(c), roleName: d })
341
+ ] }, c)),
342
+ /* @__PURE__ */ t("td", { className: "py-1 text-center", children: /* @__PURE__ */ r("div", { className: "inline-flex items-center", children: [
343
+ /* @__PURE__ */ t(
344
+ F,
345
+ {
346
+ ariaLabel: `Email for ${e.label}`,
347
+ value: W(e.key),
348
+ options: L,
349
+ onChange: (c) => P(e.key, "email", c)
350
+ }
351
+ ),
352
+ /* @__PURE__ */ t(
353
+ q,
354
+ {
355
+ show: k && X(e.key) === "role",
356
+ roleName: d
357
+ }
358
+ )
359
+ ] }) }),
360
+ (p == null ? void 0 : p.available) && /* @__PURE__ */ r("td", { className: "py-1.5 text-center", children: [
361
+ /* @__PURE__ */ t(
362
+ "input",
363
+ {
364
+ type: "checkbox",
365
+ checked: o.teams === !0,
366
+ onChange: () => P(e.key, "teams", o.teams !== !0),
367
+ disabled: b || y === "person" && !p.linked,
368
+ "aria-label": `Teams for ${e.label}`,
369
+ "data-tip": y === "person" && !p.linked ? "Connect Teams first" : void 0,
370
+ "data-notify-channel": `${e.key}:teams`,
371
+ className: "h-3.5 w-3.5 align-middle"
372
+ }
373
+ ),
374
+ /* @__PURE__ */ t(q, { show: s("teams"), roleName: d })
375
+ ] }),
376
+ w && /* @__PURE__ */ t("td", { className: "py-1 text-center", children: w.cell(e.key, e.label) }),
377
+ /* @__PURE__ */ r("td", { className: "py-1.5 text-center", children: [
378
+ /* @__PURE__ */ t(
379
+ "input",
380
+ {
381
+ type: "checkbox",
382
+ checked: o.quiet_override === !0,
383
+ onChange: () => P(
384
+ e.key,
385
+ "quiet_override",
386
+ o.quiet_override === !0 ? H(e.key).quiet_override === !0 ? !1 : void 0 : !0
387
+ ),
388
+ disabled: b,
389
+ "aria-label": `Let ${e.label} through quiet hours`,
390
+ "data-quiet-override": e.key,
391
+ className: "h-3.5 w-3.5 align-middle"
392
+ }
393
+ ),
394
+ /* @__PURE__ */ t(q, { show: s("quiet_override"), roleName: d })
395
+ ] }),
396
+ k && /* @__PURE__ */ t("td", { className: "py-1 text-center", children: /* @__PURE__ */ t(
397
+ "button",
398
+ {
399
+ type: "button",
400
+ onClick: () => K(e.key),
401
+ disabled: b,
402
+ "data-tip": "Back to the role default for this category",
403
+ "aria-label": `Use the role default for ${e.label}`,
404
+ className: M(
405
+ "rounded p-0.5 text-slate-400 hover:bg-muted hover:text-slate-700 dark:hover:text-slate-200",
406
+ !i && "invisible"
407
+ ),
408
+ children: /* @__PURE__ */ t(U, { className: "h-3 w-3" })
409
+ }
410
+ ) })
411
+ ] }, e.key);
412
+ }) })
413
+ ] }),
414
+ /* @__PURE__ */ r("div", { className: "flex flex-wrap items-center gap-2 text-[11.5px] text-slate-500 dark:text-muted-foreground", children: [
415
+ /* @__PURE__ */ t("span", { children: "All email:" }),
416
+ L.map((e) => /* @__PURE__ */ t(
417
+ "button",
418
+ {
419
+ type: "button",
420
+ disabled: b,
421
+ onClick: () => {
422
+ O((o) => {
423
+ const s = o.matrix ?? {};
424
+ for (const i of Q)
425
+ s[i.key] = { ...s[i.key] ?? {}, email: e.value };
426
+ o.matrix = s;
427
+ }), e.value !== "off" && (A == null || A(e.value));
428
+ },
429
+ className: "rounded border border-slate-200 px-2 py-0.5 hover:border-slate-400 hover:text-slate-800 dark:border-border dark:hover:text-foreground",
430
+ children: e.label
431
+ },
432
+ e.value
433
+ ))
434
+ ] }),
435
+ /* @__PURE__ */ r("div", { className: "space-y-2 rounded-md border border-slate-100 bg-slate-50/60 px-3 py-2 dark:border-border/60 dark:bg-background/40", children: [
436
+ /* @__PURE__ */ r("p", { className: "text-[12px] font-medium text-slate-700 dark:text-foreground", children: [
437
+ "If it stays unread",
438
+ /* @__PURE__ */ r("span", { className: "ml-1.5 text-[11px] font-normal text-slate-400", children: [
439
+ "one notification climbs channels on a schedule — push, then email — and stops the moment",
440
+ y === "role" ? " it is" : " you",
441
+ " read",
442
+ y === "role" ? "" : " it",
443
+ ". A channel that already delivered it is not repeated."
444
+ ] })
445
+ ] }),
446
+ /* @__PURE__ */ r("table", { className: "w-full text-[12px]", children: [
447
+ /* @__PURE__ */ t("thead", { children: /* @__PURE__ */ r("tr", { className: "text-left text-[10.5px] uppercase tracking-wide text-slate-400", children: [
448
+ /* @__PURE__ */ t("th", { className: "py-1 font-semibold", children: "Category" }),
449
+ /* @__PURE__ */ r("th", { className: "py-1 font-semibold", children: [
450
+ "Push after",
451
+ /* @__PURE__ */ t(
452
+ N,
453
+ {
454
+ text: `Still unread after this long → a browser / device push (needs a registered device; quiet hours do not apply to escalations ${Z} asked for).`
455
+ }
456
+ )
457
+ ] }),
458
+ /* @__PURE__ */ r("th", { className: "py-1 font-semibold", children: [
459
+ "Email after",
460
+ /* @__PURE__ */ t(N, { text: "Still unread after this long → one email with the notification, regardless of the Email column above." })
461
+ ] }),
462
+ k && /* @__PURE__ */ t("th", { className: "w-6 py-1", "aria-label": "Reset" })
463
+ ] }) }),
464
+ /* @__PURE__ */ t("tbody", { children: Q.map((e) => {
465
+ var s, i;
466
+ const o = ((s = _.escalation) == null ? void 0 : s[e.key]) ?? {};
467
+ return /* @__PURE__ */ r("tr", { className: "border-t border-slate-100 dark:border-border", children: [
468
+ /* @__PURE__ */ t("td", { className: "py-1 text-slate-700 dark:text-foreground", children: e.label }),
469
+ D.map((n) => /* @__PURE__ */ t("td", { className: "py-1", children: /* @__PURE__ */ r("span", { className: "inline-flex items-center", children: [
470
+ /* @__PURE__ */ t(
471
+ F,
472
+ {
473
+ ariaLabel: `${n === "push_after_min" ? "Push" : "Email"} after for ${e.label}`,
474
+ value: o[n] ? String(o[n]) : "",
475
+ options: J,
476
+ onChange: (u) => z(e.key, n, u ? Number(u) : null)
477
+ }
478
+ ),
479
+ /* @__PURE__ */ t(
480
+ q,
481
+ {
482
+ show: k && Y(a, l, (u) => {
483
+ var c, g;
484
+ return (g = (c = u == null ? void 0 : u.escalation) == null ? void 0 : c[e.key]) == null ? void 0 : g[n];
485
+ }) === "role",
486
+ roleName: d
487
+ }
488
+ )
489
+ ] }) }, n)),
490
+ k && /* @__PURE__ */ t("td", { className: "py-1 text-center", children: /* @__PURE__ */ t(
491
+ "button",
492
+ {
493
+ type: "button",
494
+ onClick: () => V(e.key),
495
+ disabled: b,
496
+ "data-tip": "Back to the role default for this category",
497
+ "aria-label": `Use the role default escalation for ${e.label}`,
498
+ className: M(
499
+ "rounded p-0.5 text-slate-400 hover:bg-muted hover:text-slate-700 dark:hover:text-slate-200",
500
+ !((i = a.escalation) != null && i[e.key]) && "invisible"
501
+ ),
502
+ children: /* @__PURE__ */ t(U, { className: "h-3 w-3" })
503
+ }
504
+ ) })
505
+ ] }, e.key);
506
+ }) })
507
+ ] })
508
+ ] })
509
+ ] });
510
+ }
511
+ export {
512
+ Q as NOTIFY_RULE_CATS,
513
+ ne as NotificationRulesEditor,
514
+ te as mergeNotifyRulesClient
515
+ };
@@ -0,0 +1,137 @@
1
+ import { jsxs as r, jsx as a } from "react/jsx-runtime";
2
+ import { useMutation as k } from "@tanstack/react-query";
3
+ import { FlaskConical as N } from "lucide-react";
4
+ import { useState as x } from "react";
5
+ import { useNivaroClient as v } from "../../context.js";
6
+ import { post as w } from "../../lib/commands.js";
7
+ import { cn as T } from "../../lib/utils.js";
8
+ import { Popover as C, PopoverTrigger as _, PopoverContent as S } from "../ui/popover.js";
9
+ import { SimpleSelectXs as f } from "../ui/SimpleSelect.js";
10
+ import { NOTIFY_RULE_CATS as E } from "./NotificationRulesEditor.js";
11
+ import { WHY_LAYER_LABELS as A } from "./why-lines.js";
12
+ const L = [
13
+ { value: "now", label: "Right now" },
14
+ { value: "night", label: "Tonight at 11 PM" }
15
+ ];
16
+ function P(d = /* @__PURE__ */ new Date()) {
17
+ const l = new Intl.DateTimeFormat("en-CA", {
18
+ timeZone: "America/New_York",
19
+ year: "numeric",
20
+ month: "2-digit",
21
+ day: "2-digit"
22
+ }).format(d);
23
+ for (const m of [4, 5]) {
24
+ const s = /* @__PURE__ */ new Date(`${l}T23:00:00Z`);
25
+ if (s.setUTCHours(s.getUTCHours() + m), new Intl.DateTimeFormat("en-US", {
26
+ timeZone: "America/New_York",
27
+ hour: "2-digit",
28
+ hour12: !1
29
+ }).format(s) === "23") return s.toISOString();
30
+ }
31
+ return d.toISOString();
32
+ }
33
+ function Z() {
34
+ var c;
35
+ const d = v(), [l, m] = x("workflow"), [s, u] = x("now"), i = k({
36
+ mutationFn: () => d.request(
37
+ w("/notifications/test-my-rules", {
38
+ category: l,
39
+ email: !0,
40
+ ...s === "night" ? { at: P() } : {}
41
+ })
42
+ ).then((t) => t.data)
43
+ }), e = i.data, g = (t) => e ? t === "email" ? e.decision.email === "send" ? "Emailed now" : e.decision.email === "deferred" ? "Daily summary" : e.decision.email === "no_address" ? "No address on file" : "No email" : t === "push" && e.decision.push && e.push_subscriptions === 0 ? "Allowed, but no device registered" : e.decision[t] ? "Yes" : "No" : null, b = (t) => (e == null ? void 0 : e.decision.reasons.find((o) => o.channel === t)) ?? (e == null ? void 0 : e.decision.reasons.find((o) => o.channel === "all")), y = !!(e != null && e.decision.reasons.some((t) => t.channel === "teams"));
44
+ return /* @__PURE__ */ r(C, { children: [
45
+ /* @__PURE__ */ a(_, { asChild: !0, children: /* @__PURE__ */ r(
46
+ "button",
47
+ {
48
+ type: "button",
49
+ "data-test-my-rules": !0,
50
+ className: "inline-flex items-center gap-1 rounded-md border border-slate-200 px-2 py-1 text-[11.5px] font-medium text-slate-600 hover:border-slate-400 hover:text-slate-800 dark:border-border dark:text-muted-foreground dark:hover:text-foreground",
51
+ children: [
52
+ /* @__PURE__ */ a(N, { className: "h-3.5 w-3.5" }),
53
+ "Test my rules"
54
+ ]
55
+ }
56
+ ) }),
57
+ /* @__PURE__ */ r(S, { align: "start", className: "w-[380px] p-3 text-[12px]", children: [
58
+ /* @__PURE__ */ a("p", { className: "font-semibold text-slate-800 dark:text-slate-100", children: "Test my rules" }),
59
+ /* @__PURE__ */ a("p", { className: "mt-0.5 text-[11px] text-slate-500 dark:text-muted-foreground", children: "What would happen to a notification for you, and which rule decides each channel. Nothing is sent." }),
60
+ /* @__PURE__ */ r("div", { className: "mt-2 flex flex-wrap items-center gap-2", children: [
61
+ /* @__PURE__ */ a(
62
+ f,
63
+ {
64
+ ariaLabel: "Category to test",
65
+ value: l,
66
+ options: E.map((t) => ({ value: t.key, label: t.label })),
67
+ onChange: (t) => m(t)
68
+ }
69
+ ),
70
+ /* @__PURE__ */ a(
71
+ f,
72
+ {
73
+ ariaLabel: "When",
74
+ value: s,
75
+ options: L,
76
+ onChange: (t) => u(t)
77
+ }
78
+ ),
79
+ /* @__PURE__ */ a(
80
+ "button",
81
+ {
82
+ type: "button",
83
+ disabled: i.isPending,
84
+ onClick: () => i.mutate(),
85
+ className: "h-6 rounded bg-nvr-cyan px-2 text-[11.5px] font-semibold text-white disabled:opacity-50",
86
+ children: i.isPending ? "Testing…" : "Test"
87
+ }
88
+ )
89
+ ] }),
90
+ i.isError && /* @__PURE__ */ a("p", { className: "mt-2 text-[11px] text-red-600 dark:text-red-400", children: "The test could not run — try again." }),
91
+ e && /* @__PURE__ */ r("div", { className: "mt-2 space-y-1", "data-test-my-rules-result": !0, children: [
92
+ e.decision.dropped ? /* @__PURE__ */ r("p", { className: "rounded border border-red-200 bg-red-50 px-2 py-1 text-red-800 dark:border-red-500/30 dark:bg-red-500/10 dark:text-red-300", children: [
93
+ "Nothing would be delivered — ",
94
+ (c = e.decision.reasons[0]) == null ? void 0 : c.text
95
+ ] }) : /* @__PURE__ */ a("ul", { className: "divide-y divide-slate-100 rounded border border-slate-100 dark:divide-border/60 dark:border-border/60", children: ["inapp", "push", "email", ...y ? ["teams"] : []].map(
96
+ (t) => {
97
+ var p, h;
98
+ const o = b(t), n = (o == null ? void 0 : o.layer) ?? ((p = e.layers) == null ? void 0 : p[t]);
99
+ return /* @__PURE__ */ r("li", { className: "px-2 py-1", "data-test-channel": t, children: [
100
+ /* @__PURE__ */ r("span", { className: "font-medium text-slate-700 dark:text-slate-200", children: [
101
+ t === "inapp" ? "In-app" : t === "push" ? "Push" : t === "email" ? "Email" : "Teams",
102
+ ":"
103
+ ] }),
104
+ " ",
105
+ g(t),
106
+ n && /* @__PURE__ */ r(
107
+ "span",
108
+ {
109
+ className: T(
110
+ "ml-1 rounded px-1 text-[10px]",
111
+ n === "person" && "bg-sky-50 text-sky-700 dark:bg-sky-500/15 dark:text-sky-300",
112
+ n === "role" && "bg-violet-50 text-violet-700 dark:bg-violet-500/15 dark:text-violet-300",
113
+ n === "system" && "bg-slate-100 text-slate-500 dark:bg-muted dark:text-slate-400"
114
+ ),
115
+ children: [
116
+ A[n],
117
+ n === "role" && ((h = e.role) != null && h.name) ? ` · ${e.role.name}` : ""
118
+ ]
119
+ }
120
+ ),
121
+ o && /* @__PURE__ */ a("p", { className: "text-[10.5px] text-slate-500 dark:text-muted-foreground", children: o.text })
122
+ ] }, t);
123
+ }
124
+ ) }),
125
+ /* @__PURE__ */ r("p", { className: "text-[10.5px] text-slate-400", children: [
126
+ e.category_label,
127
+ " · ",
128
+ new Date(e.evaluated_at).toLocaleString(),
129
+ e.quiet_hours ? ` · quiet hours ${e.quiet_hours}` : ""
130
+ ] })
131
+ ] })
132
+ ] })
133
+ ] });
134
+ }
135
+ export {
136
+ Z as TestMyRules
137
+ };
@@ -0,0 +1,24 @@
1
+ import { formatSnapshotValue as t } from "./as-it-was.js";
2
+ const i = (l) => l.replace(/_/g, " ").replace(/\b\w/g, (e) => e.toUpperCase());
3
+ function a(l) {
4
+ return ((l == null ? void 0 : l.changes) ?? []).map((e) => {
5
+ const f = e.old_label !== void 0 || e.new_label !== void 0, n = {
6
+ field: e.field,
7
+ label: e.label,
8
+ type: e.type,
9
+ interface: null,
10
+ format: null,
11
+ m2o: f ? "related" : null
12
+ };
13
+ return {
14
+ field: e.field,
15
+ label: e.label || i(e.field),
16
+ old: e.diff ? "" : t(e.old, n, e.old_label ?? null),
17
+ new: e.diff ? "" : t(e.new, n, e.new_label ?? null),
18
+ diff: e.diff ?? null
19
+ };
20
+ });
21
+ }
22
+ export {
23
+ a as changeLines
24
+ };