@burdenoff/microfe-bigconsole 2026.1004.6 → 2026.1004.8

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 (38) hide show
  1. package/dist/bigconsole/BigConsoleRoutes.d.ts.map +1 -1
  2. package/dist/bigconsole/BigConsoleRoutes.js +35 -31
  3. package/dist/bigconsole/BigConsoleRoutes.js.map +1 -1
  4. package/dist/bigconsole/components/datasink/AlertExecutionHistory.d.ts +11 -0
  5. package/dist/bigconsole/components/datasink/AlertExecutionHistory.d.ts.map +1 -0
  6. package/dist/bigconsole/components/datasink/AlertExecutionHistory.js +248 -0
  7. package/dist/bigconsole/components/datasink/AlertExecutionHistory.js.map +1 -0
  8. package/dist/bigconsole/components/datasink/AlertRuleEditor.d.ts +23 -0
  9. package/dist/bigconsole/components/datasink/AlertRuleEditor.d.ts.map +1 -0
  10. package/dist/bigconsole/components/datasink/AlertRuleEditor.js +357 -0
  11. package/dist/bigconsole/components/datasink/AlertRuleEditor.js.map +1 -0
  12. package/dist/bigconsole/components/widgets/kanban/KanbanView.d.ts.map +1 -1
  13. package/dist/bigconsole/components/widgets/kanban/KanbanView.js +1 -1
  14. package/dist/bigconsole/components/widgets/kanban/KanbanView.js.map +1 -1
  15. package/dist/bigconsole/hooks/useDataSinkAlerts.d.ts +146 -0
  16. package/dist/bigconsole/hooks/useDataSinkAlerts.d.ts.map +1 -0
  17. package/dist/bigconsole/hooks/useDataSinkAlerts.js +221 -0
  18. package/dist/bigconsole/hooks/useDataSinkAlerts.js.map +1 -0
  19. package/dist/bigconsole/pages/DataSinkAlertsPage.d.ts +2 -0
  20. package/dist/bigconsole/pages/DataSinkAlertsPage.d.ts.map +1 -0
  21. package/dist/bigconsole/pages/DataSinkAlertsPage.js +422 -0
  22. package/dist/bigconsole/pages/DataSinkAlertsPage.js.map +1 -0
  23. package/dist/bigconsole/pages/DataSinkViewPage.d.ts.map +1 -1
  24. package/dist/bigconsole/pages/DataSinkViewPage.js +235 -229
  25. package/dist/bigconsole/pages/DataSinkViewPage.js.map +1 -1
  26. package/dist/bigconsole/utils/alertRules.d.ts +40 -0
  27. package/dist/bigconsole/utils/alertRules.d.ts.map +1 -0
  28. package/dist/bigconsole/utils/alertRules.js +83 -0
  29. package/dist/bigconsole/utils/alertRules.js.map +1 -0
  30. package/dist/generated/wspace-operations.d.ts +376 -0
  31. package/dist/generated/wspace-operations.d.ts.map +1 -1
  32. package/dist/generated/wspace-operations.js +321 -200
  33. package/dist/generated/wspace-operations.js.map +1 -1
  34. package/dist/generated/wspace-types.d.ts +122 -0
  35. package/dist/generated/wspace-types.d.ts.map +1 -1
  36. package/dist/generated/wspace-types.js +7 -1
  37. package/dist/generated/wspace-types.js.map +1 -1
  38. package/package.json +1 -1
@@ -0,0 +1,23 @@
1
+ import { AlertChannel, AlertPreview } from '../../hooks/useDataSinkAlerts';
2
+ import { AlertDraft } from '../../utils/alertRules';
3
+
4
+ interface Props {
5
+ draft: AlertDraft;
6
+ channels: AlertChannel[];
7
+ channelError: string | null;
8
+ canUpdate: boolean;
9
+ pending: boolean;
10
+ previewing: boolean;
11
+ preview: AlertPreview | null;
12
+ previewError: string | null;
13
+ previewStale: boolean;
14
+ error: string | null;
15
+ onChange: (draft: AlertDraft) => void;
16
+ onSave: () => void;
17
+ onCancel: () => void;
18
+ onPreview: () => void;
19
+ onReloadChannels: () => void;
20
+ }
21
+ export declare function AlertRuleEditor(props: Props): import("react").JSX.Element;
22
+ export {};
23
+ //# sourceMappingURL=AlertRuleEditor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AlertRuleEditor.d.ts","sourceRoot":"","sources":["../../../../src/bigconsole/components/datasink/AlertRuleEditor.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAChF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAGzD,UAAU,KAAK;IACb,KAAK,EAAE,UAAU,CAAC;IAClB,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,YAAY,GAAG,IAAI,CAAC;IAC7B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,EAAE,OAAO,CAAC;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACtC,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,gBAAgB,EAAE,MAAM,IAAI,CAAC;CAC9B;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,KAAK,+BAgY3C"}
@@ -0,0 +1,357 @@
1
+ import { SECTION_FOCUS_RING as e } from "../common/sectionStyles.js";
2
+ import { Section as t } from "../common/Section.js";
3
+ import "../common/index.js";
4
+ import { AlertSeverity as n, DataSinkAlertChannel as r, DataSinkAlertConditionType as i } from "../../../generated/wspace-types.js";
5
+ import { Loader2 as a, Play as o, Plus as s, Save as c, Trash2 as l } from "lucide-react";
6
+ import { Button as u } from "@burdenoff/fe-libs/ui";
7
+ import { jsx as d, jsxs as f } from "react/jsx-runtime";
8
+ import { useI18n as p } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
9
+ //#region src/bigconsole/components/datasink/AlertRuleEditor.tsx
10
+ var m = `w-full min-w-0 rounded-control border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary ${e}`;
11
+ function h(h) {
12
+ let { t: g } = p(), { draft: _, channels: v, channelError: y, canUpdate: b, pending: x, previewing: S, preview: C, previewError: w, previewStale: T, error: E, onChange: D, onSave: O, onCancel: k, onPreview: A, onReloadChannels: j } = h, M = x || !b, N = v.filter((e) => e.status.toUpperCase() === "ACTIVE"), P = (e) => D({
13
+ ..._,
14
+ ...e
15
+ });
16
+ return /* @__PURE__ */ d(t, {
17
+ title: g("bigconsole.alerts.rule.title", "Alert rule"),
18
+ headingLevel: 2,
19
+ emphasis: "primary",
20
+ children: /* @__PURE__ */ f("form", {
21
+ onSubmit: (e) => {
22
+ e.preventDefault(), O();
23
+ },
24
+ className: "flex min-w-0 flex-col gap-form-gap",
25
+ "aria-label": g("bigconsole.alerts.rule.form", "Alert rule editor"),
26
+ children: [
27
+ !b && /* @__PURE__ */ d("p", {
28
+ className: "text-sm text-text-secondary",
29
+ children: g("bigconsole.alerts.rule.readOnly", "You have read-only access to these alert rules.")
30
+ }),
31
+ E && /* @__PURE__ */ d("p", {
32
+ role: "alert",
33
+ className: "break-words text-sm text-status-error-text",
34
+ children: E
35
+ }),
36
+ /* @__PURE__ */ f("div", { children: [/* @__PURE__ */ d("label", {
37
+ htmlFor: "alert-name",
38
+ className: "mb-1 block text-sm font-medium text-text-primary",
39
+ children: g("bigconsole.alerts.rule.name", "Name")
40
+ }), /* @__PURE__ */ d("input", {
41
+ id: "alert-name",
42
+ className: m,
43
+ value: _.name,
44
+ onChange: (e) => P({ name: e.target.value }),
45
+ required: !0,
46
+ maxLength: 200,
47
+ disabled: M
48
+ })] }),
49
+ /* @__PURE__ */ f("div", {
50
+ className: "grid min-w-0 gap-form-gap sm:grid-cols-2",
51
+ children: [/* @__PURE__ */ f("div", { children: [/* @__PURE__ */ d("label", {
52
+ htmlFor: "alert-condition-type",
53
+ className: "mb-1 block text-sm font-medium text-text-primary",
54
+ children: g("bigconsole.alerts.rule.language", "Condition language")
55
+ }), /* @__PURE__ */ f("select", {
56
+ id: "alert-condition-type",
57
+ className: m,
58
+ value: _.conditionType,
59
+ onChange: (e) => P({ conditionType: e.target.value === i.Jsonata ? i.Jsonata : i.Jq }),
60
+ disabled: M,
61
+ children: [/* @__PURE__ */ d("option", {
62
+ value: i.Jq,
63
+ children: "JQ"
64
+ }), /* @__PURE__ */ d("option", {
65
+ value: i.Jsonata,
66
+ children: "JSONata"
67
+ })]
68
+ })] }), /* @__PURE__ */ f("div", { children: [/* @__PURE__ */ d("label", {
69
+ htmlFor: "alert-severity",
70
+ className: "mb-1 block text-sm font-medium text-text-primary",
71
+ children: g("bigconsole.alerts.rule.severity", "Severity")
72
+ }), /* @__PURE__ */ d("select", {
73
+ id: "alert-severity",
74
+ className: m,
75
+ value: _.severity,
76
+ onChange: (e) => P({ severity: Object.values(n).find((t) => t === e.target.value) ?? n.Medium }),
77
+ disabled: M,
78
+ children: Object.values(n).map((e) => /* @__PURE__ */ d("option", {
79
+ value: e,
80
+ children: g(`bigconsole.alerts.severity.${e}`, e.charAt(0).toUpperCase() + e.slice(1))
81
+ }, e))
82
+ })] })]
83
+ }),
84
+ /* @__PURE__ */ f("div", { children: [
85
+ /* @__PURE__ */ d("label", {
86
+ htmlFor: "alert-condition",
87
+ className: "mb-1 block text-sm font-medium text-text-primary",
88
+ children: g("bigconsole.alerts.rule.condition", "Condition")
89
+ }),
90
+ /* @__PURE__ */ d("textarea", {
91
+ id: "alert-condition",
92
+ className: `${m} min-h-32 font-mono`,
93
+ value: _.condition,
94
+ onChange: (e) => P({ condition: e.target.value }),
95
+ rows: 5,
96
+ required: !0,
97
+ disabled: M,
98
+ "aria-describedby": "alert-condition-help"
99
+ }),
100
+ /* @__PURE__ */ d("p", {
101
+ id: "alert-condition-help",
102
+ className: "mt-2 text-xs text-text-secondary",
103
+ children: g("bigconsole.alerts.rule.conditionHelp", "Evaluates the entire current sink payload and must return one boolean. Preview does not send notifications.")
104
+ }),
105
+ /* @__PURE__ */ d("p", {
106
+ className: "mt-1 break-words text-xs text-text-muted",
107
+ children: _.conditionType === i.Jq ? "JQ: any(.[]; .boarding_minutes > 240)" : "JSONata: $count($[boarding_minutes > 240]) > 0"
108
+ }),
109
+ /* @__PURE__ */ f(u, {
110
+ type: "button",
111
+ variant: "secondary",
112
+ size: "sm",
113
+ onClick: A,
114
+ disabled: x || S || !_.condition.trim(),
115
+ className: "mt-3",
116
+ children: [S ? /* @__PURE__ */ d(a, { className: "mr-2 h-4 w-4 animate-spin" }) : /* @__PURE__ */ d(o, { className: "mr-2 h-4 w-4" }), S ? g("bigconsole.alerts.rule.previewing", "Evaluating…") : g("bigconsole.alerts.rule.preview", "Preview condition")]
117
+ }),
118
+ w && /* @__PURE__ */ d("p", {
119
+ role: "alert",
120
+ className: "mt-2 break-words text-sm text-status-error-text",
121
+ children: w
122
+ }),
123
+ T && /* @__PURE__ */ d("p", {
124
+ role: "status",
125
+ className: "mt-2 text-xs text-text-secondary",
126
+ children: g("bigconsole.alerts.rule.previewStale", "The condition or data changed. Preview again to see the current result.")
127
+ }),
128
+ C && !T && /* @__PURE__ */ f("div", {
129
+ role: "status",
130
+ className: "mt-3 rounded-control border border-border-subtle bg-bg-sunken p-3 text-sm",
131
+ children: [C.error ? /* @__PURE__ */ d("p", {
132
+ className: "break-words text-status-error-text",
133
+ children: C.error
134
+ }) : /* @__PURE__ */ d("p", {
135
+ className: "font-medium text-text-primary",
136
+ children: C.matched ? g("bigconsole.alerts.rule.matched", "Condition matched") : g("bigconsole.alerts.rule.noMatch", "Condition did not match")
137
+ }), /* @__PURE__ */ f("p", {
138
+ className: "mt-1 text-xs text-text-secondary",
139
+ children: [
140
+ g("bigconsole.alerts.rule.previewMetadata", "Evaluated data version"),
141
+ " ",
142
+ C.version,
143
+ " ·",
144
+ " ",
145
+ C.durationMs,
146
+ " ms"
147
+ ]
148
+ })]
149
+ })
150
+ ] }),
151
+ /* @__PURE__ */ f("fieldset", {
152
+ disabled: M,
153
+ className: "min-w-0",
154
+ children: [
155
+ /* @__PURE__ */ d("legend", {
156
+ className: "mb-2 text-sm font-medium text-text-primary",
157
+ children: g("bigconsole.alerts.rule.destinations", "Delivery destinations")
158
+ }),
159
+ /* @__PURE__ */ d("p", {
160
+ className: "mb-3 text-xs text-text-secondary",
161
+ children: g("bigconsole.alerts.rule.destinationHelp", "Choose an available active delivery channel and its actual recipient.")
162
+ }),
163
+ y && /* @__PURE__ */ f("div", {
164
+ className: "mb-3 text-sm",
165
+ children: [/* @__PURE__ */ d("p", {
166
+ role: "alert",
167
+ className: "break-words text-status-error-text",
168
+ children: y
169
+ }), /* @__PURE__ */ d(u, {
170
+ type: "button",
171
+ variant: "secondary",
172
+ size: "sm",
173
+ onClick: j,
174
+ children: g("bigconsole.alerts.rule.reloadChannels", "Reload channels")
175
+ })]
176
+ }),
177
+ !y && N.length === 0 && /* @__PURE__ */ d("p", {
178
+ className: "mb-3 text-sm text-text-secondary",
179
+ children: g("bigconsole.alerts.rule.noChannels", "No active delivery channels are available to BigConsole in this organization. You can save this rule disabled.")
180
+ }),
181
+ !_.deliveries.length && /* @__PURE__ */ d("p", {
182
+ className: "mb-3 text-xs text-text-muted",
183
+ children: g("bigconsole.alerts.rule.noDestinations", "No delivery destinations selected.")
184
+ }),
185
+ _.deliveries.map((e, t) => /* @__PURE__ */ f("div", {
186
+ className: "mb-3 min-w-0 rounded-control border border-border-subtle p-3",
187
+ children: [
188
+ /* @__PURE__ */ f("label", {
189
+ htmlFor: `alert-channel-${e.key}`,
190
+ className: "mb-1 block text-xs font-medium text-text-primary",
191
+ children: [
192
+ g("bigconsole.alerts.rule.channel", "Configured channel"),
193
+ " ",
194
+ t + 1
195
+ ]
196
+ }),
197
+ /* @__PURE__ */ f("select", {
198
+ id: `alert-channel-${e.key}`,
199
+ className: m,
200
+ value: e.channelId,
201
+ onChange: (t) => {
202
+ let n = v.find((e) => e.id === t.target.value);
203
+ P({ deliveries: _.deliveries.map((t) => t.key === e.key ? {
204
+ ...t,
205
+ channelId: n?.id ?? "",
206
+ channel: n?.platform ?? r.Email,
207
+ recipient: n?.platform === t.channel ? t.recipient : ""
208
+ } : t) });
209
+ },
210
+ children: [
211
+ /* @__PURE__ */ d("option", {
212
+ value: "",
213
+ children: g("bigconsole.alerts.rule.chooseChannel", "Choose a configured channel")
214
+ }),
215
+ e.channelId && !N.some((t) => t.id === e.channelId) && /* @__PURE__ */ f("option", {
216
+ value: e.channelId,
217
+ children: [
218
+ g("bigconsole.alerts.rule.unavailableChannel", "Unavailable channel"),
219
+ " · ",
220
+ e.channelId
221
+ ]
222
+ }),
223
+ N.map((e) => /* @__PURE__ */ f("option", {
224
+ value: e.id,
225
+ children: [
226
+ e.name,
227
+ " · ",
228
+ e.platform
229
+ ]
230
+ }, e.id))
231
+ ]
232
+ }),
233
+ /* @__PURE__ */ d("label", {
234
+ htmlFor: `alert-recipient-${e.key}`,
235
+ className: "mb-1 mt-3 block text-xs font-medium text-text-primary",
236
+ children: e.channel === r.Email ? g("bigconsole.alerts.rule.emailRecipient", "Email address") : e.channel === r.Slack ? g("bigconsole.alerts.rule.slackRecipient", "Slack recipient ID") : g("bigconsole.alerts.rule.webhookRecipient", "HTTPS webhook URL")
237
+ }),
238
+ /* @__PURE__ */ d("input", {
239
+ id: `alert-recipient-${e.key}`,
240
+ className: m,
241
+ type: e.channel === r.Email ? "email" : e.channel === r.Webhook ? "url" : "text",
242
+ value: e.recipient,
243
+ onChange: (t) => P({ deliveries: _.deliveries.map((n) => n.key === e.key ? {
244
+ ...n,
245
+ recipient: t.target.value
246
+ } : n) }),
247
+ required: !0
248
+ }),
249
+ /* @__PURE__ */ f(u, {
250
+ type: "button",
251
+ variant: "ghost",
252
+ size: "sm",
253
+ onClick: () => P({ deliveries: _.deliveries.filter((t) => t.key !== e.key) }),
254
+ className: "mt-2",
255
+ children: [/* @__PURE__ */ d(l, { className: "mr-2 h-4 w-4" }), g("bigconsole.alerts.rule.removeDestination", "Remove destination")]
256
+ })
257
+ ]
258
+ }, e.key)),
259
+ /* @__PURE__ */ f(u, {
260
+ type: "button",
261
+ variant: "secondary",
262
+ size: "sm",
263
+ onClick: () => P({ deliveries: [..._.deliveries, {
264
+ key: crypto.randomUUID(),
265
+ channel: r.Email,
266
+ channelId: "",
267
+ recipient: ""
268
+ }] }),
269
+ disabled: N.length === 0,
270
+ children: [/* @__PURE__ */ d(s, { className: "mr-2 h-4 w-4" }), g("bigconsole.alerts.rule.addDestination", "Add destination")]
271
+ })
272
+ ]
273
+ }),
274
+ /* @__PURE__ */ f("fieldset", {
275
+ disabled: M,
276
+ className: "min-w-0",
277
+ children: [
278
+ /* @__PURE__ */ d("legend", {
279
+ className: "mb-2 text-sm font-medium text-text-primary",
280
+ children: g("bigconsole.alerts.rule.throttle", "Throttle")
281
+ }),
282
+ /* @__PURE__ */ f("label", {
283
+ className: "flex items-center gap-2 text-sm text-text-primary",
284
+ children: [/* @__PURE__ */ d("input", {
285
+ type: "checkbox",
286
+ checked: _.throttleEnabled,
287
+ onChange: (e) => P({ throttleEnabled: e.target.checked }),
288
+ className: e
289
+ }), g("bigconsole.alerts.rule.limitFrequency", "Limit alert frequency")]
290
+ }),
291
+ _.throttleEnabled && /* @__PURE__ */ f("div", {
292
+ className: "mt-3 grid gap-form-gap sm:grid-cols-2",
293
+ children: [/* @__PURE__ */ f("div", { children: [/* @__PURE__ */ d("label", {
294
+ htmlFor: "alert-window",
295
+ className: "mb-1 block text-xs font-medium text-text-primary",
296
+ children: g("bigconsole.alerts.rule.window", "Time window (minutes)")
297
+ }), /* @__PURE__ */ d("input", {
298
+ id: "alert-window",
299
+ type: "number",
300
+ min: 1,
301
+ step: 1,
302
+ className: m,
303
+ value: _.windowMinutes,
304
+ onChange: (e) => P({ windowMinutes: e.target.value }),
305
+ required: !0
306
+ })] }), /* @__PURE__ */ f("div", { children: [/* @__PURE__ */ d("label", {
307
+ htmlFor: "alert-max",
308
+ className: "mb-1 block text-xs font-medium text-text-primary",
309
+ children: g("bigconsole.alerts.rule.maxAlerts", "Maximum alerts per window")
310
+ }), /* @__PURE__ */ d("input", {
311
+ id: "alert-max",
312
+ type: "number",
313
+ min: 1,
314
+ step: 1,
315
+ className: m,
316
+ value: _.maxAlerts,
317
+ onChange: (e) => P({ maxAlerts: e.target.value }),
318
+ required: !0
319
+ })] })]
320
+ })
321
+ ]
322
+ }),
323
+ /* @__PURE__ */ f("label", {
324
+ className: "flex items-start gap-2 text-sm text-text-primary",
325
+ children: [/* @__PURE__ */ d("input", {
326
+ type: "checkbox",
327
+ checked: _.enabled,
328
+ onChange: (e) => P({ enabled: e.target.checked }),
329
+ disabled: M,
330
+ className: `mt-1 ${e}`
331
+ }), /* @__PURE__ */ f("span", { children: [g("bigconsole.alerts.rule.enabled", "Rule enabled"), /* @__PURE__ */ d("span", {
332
+ className: "mt-1 block text-xs text-text-secondary",
333
+ children: g("bigconsole.alerts.rule.enabledHelp", "Enabled rules evaluate when this sink is updated or refreshed. Saving a disabled rule does not send notifications.")
334
+ })] })]
335
+ }),
336
+ /* @__PURE__ */ f("div", {
337
+ className: "flex flex-wrap justify-end gap-control-gap border-t border-border-subtle pt-4",
338
+ children: [/* @__PURE__ */ d(u, {
339
+ type: "button",
340
+ variant: "secondary",
341
+ onClick: k,
342
+ disabled: x,
343
+ children: g("bigconsole.alerts.rule.cancel", "Cancel")
344
+ }), /* @__PURE__ */ f(u, {
345
+ type: "submit",
346
+ disabled: M,
347
+ children: [x ? /* @__PURE__ */ d(a, { className: "mr-2 h-4 w-4 animate-spin" }) : /* @__PURE__ */ d(c, { className: "mr-2 h-4 w-4" }), x ? g("bigconsole.alerts.rule.saving", "Saving…") : g("bigconsole.alerts.rule.save", "Save rule")]
348
+ })]
349
+ })
350
+ ]
351
+ })
352
+ });
353
+ }
354
+ //#endregion
355
+ export { h as AlertRuleEditor };
356
+
357
+ //# sourceMappingURL=AlertRuleEditor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AlertRuleEditor.js","names":[],"sources":["../../../../src/bigconsole/components/datasink/AlertRuleEditor.tsx"],"sourcesContent":["import type { FormEvent } from 'react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { Loader2, Plus, Save, Trash2, Play } from 'lucide-react';\nimport { Section, SECTION_FOCUS_RING } from '../common';\nimport { AlertSeverity, DataSinkAlertConditionType, DataSinkAlertChannel } from '../../../generated/wspace-types';\nimport type { AlertChannel, AlertPreview } from '../../hooks/useDataSinkAlerts';\nimport type { AlertDraft } from '../../utils/alertRules';\n\nconst FIELD = `w-full min-w-0 rounded-control border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary ${SECTION_FOCUS_RING}`;\ninterface Props {\n draft: AlertDraft;\n channels: AlertChannel[];\n channelError: string | null;\n canUpdate: boolean;\n pending: boolean;\n previewing: boolean;\n preview: AlertPreview | null;\n previewError: string | null;\n previewStale: boolean;\n error: string | null;\n onChange: (draft: AlertDraft) => void;\n onSave: () => void;\n onCancel: () => void;\n onPreview: () => void;\n onReloadChannels: () => void;\n}\n\nexport function AlertRuleEditor(props: Props) {\n const { t } = useI18n();\n const {\n draft,\n channels,\n channelError,\n canUpdate,\n pending,\n previewing,\n preview,\n previewError,\n previewStale,\n error,\n onChange,\n onSave,\n onCancel,\n onPreview,\n onReloadChannels,\n } = props;\n const disabled = pending || !canUpdate;\n const activeChannels = channels.filter((channel) => channel.status.toUpperCase() === 'ACTIVE');\n const change = (patch: Partial<AlertDraft>) => onChange({ ...draft, ...patch });\n const submit = (event: FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n onSave();\n };\n return (\n <Section title={t('bigconsole.alerts.rule.title', 'Alert rule')} headingLevel={2} emphasis=\"primary\">\n <form\n onSubmit={submit}\n className=\"flex min-w-0 flex-col gap-form-gap\"\n aria-label={t('bigconsole.alerts.rule.form', 'Alert rule editor')}\n >\n {!canUpdate && (\n <p className=\"text-sm text-text-secondary\">\n {t('bigconsole.alerts.rule.readOnly', 'You have read-only access to these alert rules.')}\n </p>\n )}\n {error && (\n <p role=\"alert\" className=\"break-words text-sm text-status-error-text\">\n {error}\n </p>\n )}\n <div>\n <label htmlFor=\"alert-name\" className=\"mb-1 block text-sm font-medium text-text-primary\">\n {t('bigconsole.alerts.rule.name', 'Name')}\n </label>\n <input\n id=\"alert-name\"\n className={FIELD}\n value={draft.name}\n onChange={(event) => change({ name: event.target.value })}\n required\n maxLength={200}\n disabled={disabled}\n />\n </div>\n <div className=\"grid min-w-0 gap-form-gap sm:grid-cols-2\">\n <div>\n <label htmlFor=\"alert-condition-type\" className=\"mb-1 block text-sm font-medium text-text-primary\">\n {t('bigconsole.alerts.rule.language', 'Condition language')}\n </label>\n <select\n id=\"alert-condition-type\"\n className={FIELD}\n value={draft.conditionType}\n onChange={(event) =>\n change({\n conditionType:\n event.target.value === DataSinkAlertConditionType.Jsonata\n ? DataSinkAlertConditionType.Jsonata\n : DataSinkAlertConditionType.Jq,\n })\n }\n disabled={disabled}\n >\n <option value={DataSinkAlertConditionType.Jq}>JQ</option>\n <option value={DataSinkAlertConditionType.Jsonata}>JSONata</option>\n </select>\n </div>\n <div>\n <label htmlFor=\"alert-severity\" className=\"mb-1 block text-sm font-medium text-text-primary\">\n {t('bigconsole.alerts.rule.severity', 'Severity')}\n </label>\n <select\n id=\"alert-severity\"\n className={FIELD}\n value={draft.severity}\n onChange={(event) =>\n change({\n severity:\n Object.values(AlertSeverity).find((value) => value === event.target.value) ?? AlertSeverity.Medium,\n })\n }\n disabled={disabled}\n >\n {Object.values(AlertSeverity).map((value) => (\n <option key={value} value={value}>\n {t(`bigconsole.alerts.severity.${value}`, value.charAt(0).toUpperCase() + value.slice(1))}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div>\n <label htmlFor=\"alert-condition\" className=\"mb-1 block text-sm font-medium text-text-primary\">\n {t('bigconsole.alerts.rule.condition', 'Condition')}\n </label>\n <textarea\n id=\"alert-condition\"\n className={`${FIELD} min-h-32 font-mono`}\n value={draft.condition}\n onChange={(event) => change({ condition: event.target.value })}\n rows={5}\n required\n disabled={disabled}\n aria-describedby=\"alert-condition-help\"\n />\n <p id=\"alert-condition-help\" className=\"mt-2 text-xs text-text-secondary\">\n {t(\n 'bigconsole.alerts.rule.conditionHelp',\n 'Evaluates the entire current sink payload and must return one boolean. Preview does not send notifications.'\n )}\n </p>\n <p className=\"mt-1 break-words text-xs text-text-muted\">\n {draft.conditionType === DataSinkAlertConditionType.Jq\n ? 'JQ: any(.[]; .boarding_minutes > 240)'\n : 'JSONata: $count($[boarding_minutes > 240]) > 0'}\n </p>\n <Button\n type=\"button\"\n variant=\"secondary\"\n size=\"sm\"\n onClick={onPreview}\n disabled={pending || previewing || !draft.condition.trim()}\n className=\"mt-3\"\n >\n {previewing ? <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" /> : <Play className=\"mr-2 h-4 w-4\" />}\n {previewing\n ? t('bigconsole.alerts.rule.previewing', 'Evaluating…')\n : t('bigconsole.alerts.rule.preview', 'Preview condition')}\n </Button>\n {previewError && (\n <p role=\"alert\" className=\"mt-2 break-words text-sm text-status-error-text\">\n {previewError}\n </p>\n )}\n {previewStale && (\n <p role=\"status\" className=\"mt-2 text-xs text-text-secondary\">\n {t(\n 'bigconsole.alerts.rule.previewStale',\n 'The condition or data changed. Preview again to see the current result.'\n )}\n </p>\n )}\n {preview && !previewStale && (\n <div role=\"status\" className=\"mt-3 rounded-control border border-border-subtle bg-bg-sunken p-3 text-sm\">\n {preview.error ? (\n <p className=\"break-words text-status-error-text\">{preview.error}</p>\n ) : (\n <p className=\"font-medium text-text-primary\">\n {preview.matched\n ? t('bigconsole.alerts.rule.matched', 'Condition matched')\n : t('bigconsole.alerts.rule.noMatch', 'Condition did not match')}\n </p>\n )}\n <p className=\"mt-1 text-xs text-text-secondary\">\n {t('bigconsole.alerts.rule.previewMetadata', 'Evaluated data version')} {preview.version} ·{' '}\n {preview.durationMs} ms\n </p>\n </div>\n )}\n </div>\n <fieldset disabled={disabled} className=\"min-w-0\">\n <legend className=\"mb-2 text-sm font-medium text-text-primary\">\n {t('bigconsole.alerts.rule.destinations', 'Delivery destinations')}\n </legend>\n <p className=\"mb-3 text-xs text-text-secondary\">\n {t(\n 'bigconsole.alerts.rule.destinationHelp',\n 'Choose an available active delivery channel and its actual recipient.'\n )}\n </p>\n {channelError && (\n <div className=\"mb-3 text-sm\">\n <p role=\"alert\" className=\"break-words text-status-error-text\">\n {channelError}\n </p>\n <Button type=\"button\" variant=\"secondary\" size=\"sm\" onClick={onReloadChannels}>\n {t('bigconsole.alerts.rule.reloadChannels', 'Reload channels')}\n </Button>\n </div>\n )}\n {!channelError && activeChannels.length === 0 && (\n <p className=\"mb-3 text-sm text-text-secondary\">\n {t(\n 'bigconsole.alerts.rule.noChannels',\n 'No active delivery channels are available to BigConsole in this organization. You can save this rule disabled.'\n )}\n </p>\n )}\n {!draft.deliveries.length && (\n <p className=\"mb-3 text-xs text-text-muted\">\n {t('bigconsole.alerts.rule.noDestinations', 'No delivery destinations selected.')}\n </p>\n )}\n {draft.deliveries.map((delivery, index) => (\n <div key={delivery.key} className=\"mb-3 min-w-0 rounded-control border border-border-subtle p-3\">\n <label\n htmlFor={`alert-channel-${delivery.key}`}\n className=\"mb-1 block text-xs font-medium text-text-primary\"\n >\n {t('bigconsole.alerts.rule.channel', 'Configured channel')} {index + 1}\n </label>\n <select\n id={`alert-channel-${delivery.key}`}\n className={FIELD}\n value={delivery.channelId}\n onChange={(event) => {\n const channel = channels.find((item) => item.id === event.target.value);\n change({\n deliveries: draft.deliveries.map((item) =>\n item.key === delivery.key\n ? {\n ...item,\n channelId: channel?.id ?? '',\n channel: channel?.platform ?? DataSinkAlertChannel.Email,\n recipient: channel?.platform === item.channel ? item.recipient : '',\n }\n : item\n ),\n });\n }}\n >\n <option value=\"\">{t('bigconsole.alerts.rule.chooseChannel', 'Choose a configured channel')}</option>\n {delivery.channelId && !activeChannels.some((channel) => channel.id === delivery.channelId) && (\n <option value={delivery.channelId}>\n {t('bigconsole.alerts.rule.unavailableChannel', 'Unavailable channel')} · {delivery.channelId}\n </option>\n )}\n {activeChannels.map((channel) => (\n <option key={channel.id} value={channel.id}>\n {channel.name} · {channel.platform}\n </option>\n ))}\n </select>\n <label\n htmlFor={`alert-recipient-${delivery.key}`}\n className=\"mb-1 mt-3 block text-xs font-medium text-text-primary\"\n >\n {delivery.channel === DataSinkAlertChannel.Email\n ? t('bigconsole.alerts.rule.emailRecipient', 'Email address')\n : delivery.channel === DataSinkAlertChannel.Slack\n ? t('bigconsole.alerts.rule.slackRecipient', 'Slack recipient ID')\n : t('bigconsole.alerts.rule.webhookRecipient', 'HTTPS webhook URL')}\n </label>\n <input\n id={`alert-recipient-${delivery.key}`}\n className={FIELD}\n type={\n delivery.channel === DataSinkAlertChannel.Email\n ? 'email'\n : delivery.channel === DataSinkAlertChannel.Webhook\n ? 'url'\n : 'text'\n }\n value={delivery.recipient}\n onChange={(event) =>\n change({\n deliveries: draft.deliveries.map((item) =>\n item.key === delivery.key ? { ...item, recipient: event.target.value } : item\n ),\n })\n }\n required\n />\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => change({ deliveries: draft.deliveries.filter((item) => item.key !== delivery.key) })}\n className=\"mt-2\"\n >\n <Trash2 className=\"mr-2 h-4 w-4\" />\n {t('bigconsole.alerts.rule.removeDestination', 'Remove destination')}\n </Button>\n </div>\n ))}\n <Button\n type=\"button\"\n variant=\"secondary\"\n size=\"sm\"\n onClick={() =>\n change({\n deliveries: [\n ...draft.deliveries,\n { key: crypto.randomUUID(), channel: DataSinkAlertChannel.Email, channelId: '', recipient: '' },\n ],\n })\n }\n disabled={activeChannels.length === 0}\n >\n <Plus className=\"mr-2 h-4 w-4\" />\n {t('bigconsole.alerts.rule.addDestination', 'Add destination')}\n </Button>\n </fieldset>\n <fieldset disabled={disabled} className=\"min-w-0\">\n <legend className=\"mb-2 text-sm font-medium text-text-primary\">\n {t('bigconsole.alerts.rule.throttle', 'Throttle')}\n </legend>\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={draft.throttleEnabled}\n onChange={(event) => change({ throttleEnabled: event.target.checked })}\n className={SECTION_FOCUS_RING}\n />\n {t('bigconsole.alerts.rule.limitFrequency', 'Limit alert frequency')}\n </label>\n {draft.throttleEnabled && (\n <div className=\"mt-3 grid gap-form-gap sm:grid-cols-2\">\n <div>\n <label htmlFor=\"alert-window\" className=\"mb-1 block text-xs font-medium text-text-primary\">\n {t('bigconsole.alerts.rule.window', 'Time window (minutes)')}\n </label>\n <input\n id=\"alert-window\"\n type=\"number\"\n min={1}\n step={1}\n className={FIELD}\n value={draft.windowMinutes}\n onChange={(event) => change({ windowMinutes: event.target.value })}\n required\n />\n </div>\n <div>\n <label htmlFor=\"alert-max\" className=\"mb-1 block text-xs font-medium text-text-primary\">\n {t('bigconsole.alerts.rule.maxAlerts', 'Maximum alerts per window')}\n </label>\n <input\n id=\"alert-max\"\n type=\"number\"\n min={1}\n step={1}\n className={FIELD}\n value={draft.maxAlerts}\n onChange={(event) => change({ maxAlerts: event.target.value })}\n required\n />\n </div>\n </div>\n )}\n </fieldset>\n <label className=\"flex items-start gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={draft.enabled}\n onChange={(event) => change({ enabled: event.target.checked })}\n disabled={disabled}\n className={`mt-1 ${SECTION_FOCUS_RING}`}\n />\n <span>\n {t('bigconsole.alerts.rule.enabled', 'Rule enabled')}\n <span className=\"mt-1 block text-xs text-text-secondary\">\n {t(\n 'bigconsole.alerts.rule.enabledHelp',\n 'Enabled rules evaluate when this sink is updated or refreshed. Saving a disabled rule does not send notifications.'\n )}\n </span>\n </span>\n </label>\n <div className=\"flex flex-wrap justify-end gap-control-gap border-t border-border-subtle pt-4\">\n <Button type=\"button\" variant=\"secondary\" onClick={onCancel} disabled={pending}>\n {t('bigconsole.alerts.rule.cancel', 'Cancel')}\n </Button>\n <Button type=\"submit\" disabled={disabled}>\n {pending ? <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" /> : <Save className=\"mr-2 h-4 w-4\" />}\n {pending ? t('bigconsole.alerts.rule.saving', 'Saving…') : t('bigconsole.alerts.rule.save', 'Save rule')}\n </Button>\n </div>\n </form>\n </Section>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"KanbanView.d.ts","sourceRoot":"","sources":["../../../../../src/bigconsole/components/widgets/kanban/KanbanView.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,KAAK,EAAE,EAAmC,MAAM,OAAO,CAAC;AACjE,OAAO,EAIL,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,gBAAgB,EACtB,MAAM,SAAS,CAAC;AACjB,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACpE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAM5D,MAAM,WAAW,eAAgB,SAAQ,iBAAiB;IACxD,qGAAqG;IACrG,CAAC,EAAE,iBAAiB,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,gBAAgB,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC/D;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;CACtC;AAmBD,eAAO,MAAM,UAAU,EAAE,EAAE,CAAC,eAAe,CAmNzC,CAAC;AAEH,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"KanbanView.d.ts","sourceRoot":"","sources":["../../../../../src/bigconsole/components/widgets/kanban/KanbanView.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,KAAK,EAAE,EAAmC,MAAM,OAAO,CAAC;AACjE,OAAO,EAIL,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,gBAAgB,EACtB,MAAM,SAAS,CAAC;AACjB,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACpE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAM5D,MAAM,WAAW,eAAgB,SAAQ,iBAAiB;IACxD,qGAAqG;IACrG,CAAC,EAAE,iBAAiB,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,gBAAgB,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC/D;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;CACtC;AAmBD,eAAO,MAAM,UAAU,EAAE,EAAE,CAAC,eAAe,CAqNzC,CAAC;AAEH,eAAe,UAAU,CAAC"}
@@ -70,7 +70,7 @@ var u = r(function({ widget: r, data: u, onClick: d, t: f, onSaveMoves: p, inter
70
70
  "aria-label": `Column ${e.title}`,
71
71
  className: "flex-shrink-0 w-48 flex flex-col bg-bg-sunken/50 rounded-lg",
72
72
  onDragOver: (e) => {
73
- w.current && !b && (e.preventDefault(), e.dataTransfer.dropEffect = "move");
73
+ w.current && !b && (e.stopPropagation(), e.preventDefault(), e.dataTransfer.dropEffect = "move");
74
74
  },
75
75
  onDrop: (t) => {
76
76
  t.preventDefault(), t.stopPropagation();
@@ -1 +1 @@
1
- {"version":3,"file":"KanbanView.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/kanban/KanbanView.tsx"],"sourcesContent":["/**\n * KanbanWidget Component\n *\n * Simple kanban board view with columns and tasks.\n */\n\nimport { type FC, memo, useMemo, useRef, useState } from 'react';\nimport {\n normalizeKanban,\n applyKanbanMoves,\n validKanbanMoves,\n type KanbanData,\n type KanbanTask,\n type KanbanColumnMove,\n} from './model';\nexport type { KanbanData, KanbanTask, KanbanColumn } from './model';\nimport type { Widget } from '../../../types';\nimport type { WidgetTranslateFn } from '../utils/translate';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface KanbanViewProps extends KanbanWidgetProps {\n /** Translator — the app passes useI18n().t; the public embed passes a static fallback translator. */\n t: WidgetTranslateFn;\n interactive?: boolean;\n onSaveMoves?: (moves: KanbanColumnMove[]) => Promise<boolean>;\n}\n\nexport interface KanbanWidgetProps {\n widget: Widget;\n data?: KanbanData;\n onClick?: (task: KanbanTask) => void;\n}\n\nfunction getPriorityColor(priority?: string): string {\n switch (priority) {\n case 'high':\n return 'bg-status-error-bg/20 text-status-error-text';\n case 'medium':\n return 'bg-status-warning-bg/20 text-[var(--color-status-warning-text)]';\n case 'low':\n return 'bg-status-success-bg/20 text-status-success-text';\n default:\n return 'bg-bg-sunken text-text-secondary';\n }\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const KanbanView: FC<KanbanViewProps> = memo(function KanbanView({\n widget,\n data,\n onClick,\n t,\n onSaveMoves,\n interactive = false,\n}) {\n const config = widget.config as Record<string, unknown> | undefined;\n const { columns, tasks: sourceTasks } = useMemo(() => normalizeKanban(data, config), [data, config]);\n const [local, setLocal] = useState<{ data: unknown; widgetId: string; moves: KanbanColumnMove[] } | null>(null);\n const [saving, setSaving] = useState(false);\n const [announcement, setAnnouncement] = useState('');\n const dragging = useRef<string | null>(null);\n const moves =\n local && local.data === data && local.widgetId === widget.id\n ? local.moves\n : validKanbanMoves(config?.kanbanColumnMoves);\n const tasks = useMemo(() => applyKanbanMoves(sourceTasks, columns, moves), [sourceTasks, columns, moves]);\n const moveTask = async (taskId: string, targetColumn: string) => {\n if (!interactive || saving || !columns.some((column) => column.id === targetColumn)) return;\n const task = sourceTasks.find((item) => item.id === taskId);\n if (!task || tasks.find((item) => item.id === taskId)?.column === targetColumn) return;\n const previous = validKanbanMoves(moves);\n const next = [\n ...previous.filter((move) => move.taskId !== taskId),\n { taskId, sourceColumn: task.column, targetColumn },\n ];\n setLocal({ data, widgetId: widget.id, moves: next });\n if (!onSaveMoves || !task.stableId) {\n setAnnouncement(\n t('bigconsole.widget.kanban.move.temporary', '{{title}} moved to {{column}} for this view.', {\n title: task.title,\n column: targetColumn,\n })\n );\n return;\n }\n setSaving(true);\n let saved: boolean;\n try {\n saved = await onSaveMoves(\n next.filter((move) => sourceTasks.some((source) => source.id === move.taskId && source.stableId))\n );\n } catch {\n saved = false;\n }\n setSaving(false);\n if (!saved) {\n setLocal({ data, widgetId: widget.id, moves: previous });\n setAnnouncement(\n t(\n 'bigconsole.widget.kanban.move.failed',\n 'Could not save the move. The card was returned to its previous column.'\n )\n );\n } else {\n setAnnouncement(\n t('bigconsole.widget.kanban.move.saved', '{{title}} moved to {{column}} and saved.', {\n title: task.title,\n column: targetColumn,\n })\n );\n }\n };\n\n // Group tasks by column\n const tasksByColumn = useMemo(() => {\n const grouped = new Map<string, KanbanTask[]>();\n columns.forEach((col) => {\n grouped.set(\n col.id,\n tasks.filter((t) => t.column === col.id)\n );\n });\n return grouped;\n }, [columns, tasks]);\n\n if (!columns.length) {\n return (\n <div className=\"flex items-center justify-center h-full text-text-secondary text-sm\">\n <div className=\"text-center\">\n <svg className=\"w-12 h-12 mx-auto mb-2 text-text-muted\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={1.5}\n d=\"M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a2 2 0 00-2 2\"\n />\n </svg>\n <p>{t('bigconsole.widget.kanban.emptyState', 'No board data')}</p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"flex h-full flex-col\">\n {interactive && (\n <p role=\"status\" className=\"px-2 pt-2 text-xs text-text-secondary\">\n {announcement ||\n (onSaveMoves\n ? t('bigconsole.widget.kanban.move.editorHint', 'Moves are saved to this widget.')\n : t('bigconsole.widget.kanban.move.viewerHint', 'Moves are temporary in this view.'))}\n </p>\n )}\n <div className=\"flex min-h-0 flex-1 p-2 gap-2 overflow-x-auto\">\n {columns.map((column) => {\n const columnTasks = tasksByColumn.get(column.id) || [];\n\n return (\n <div\n key={column.id}\n aria-label={`Column ${column.title}`}\n className=\"flex-shrink-0 w-48 flex flex-col bg-bg-sunken/50 rounded-lg\"\n onDragOver={(event) => {\n if (dragging.current && !saving) {\n event.preventDefault();\n event.dataTransfer.dropEffect = 'move';\n }\n }}\n onDrop={(event) => {\n event.preventDefault();\n event.stopPropagation();\n const id = dragging.current;\n dragging.current = null;\n if (id) void moveTask(id, column.id);\n }}\n >\n {/* Column Header */}\n <div className=\"flex items-center gap-2 p-2 border-b border-border-default\">\n <div\n className=\"w-2 h-2 rounded-full\"\n style={{ backgroundColor: column.color || 'var(--color-text-muted)' }}\n />\n <span className=\"text-xs font-medium text-text-primary truncate\">{column.title}</span>\n <span className=\"ml-auto text-xs text-text-muted\">{columnTasks.length}</span>\n </div>\n\n {/* Tasks */}\n <div className=\"flex-1 p-2 space-y-2 overflow-y-auto\">\n {columnTasks.map((task) => (\n <div\n key={task.id}\n draggable={interactive && !saving}\n onDragStart={(event) => {\n event.stopPropagation();\n dragging.current = task.id;\n event.dataTransfer.effectAllowed = 'move';\n event.dataTransfer.setData('text/plain', task.id);\n }}\n onDragEnd={() => {\n dragging.current = null;\n }}\n className={`\n p-2 bg-bg-surface rounded border border-border-default\n ${onClick ? 'cursor-pointer hover:border-action-primary-bg' : ''}\n transition-colors\n `}\n onClick={() => onClick?.(task)}\n >\n <p className=\"text-xs font-medium text-text-primary line-clamp-2\">{task.title}</p>\n {task.description && (\n <p className=\"mt-1 text-xs text-text-secondary line-clamp-3\">{task.description}</p>\n )}\n {interactive && (\n <select\n aria-label={`Move ${task.title} to column`}\n value={task.column}\n disabled={saving}\n onClick={(event) => event.stopPropagation()}\n onChange={(event) => {\n void moveTask(task.id, event.target.value);\n }}\n className=\"mt-2 w-full rounded border border-border-default bg-bg-surface text-xs text-text-secondary\"\n >\n {columns.map((target) => (\n <option key={target.id} value={target.id}>\n {target.title}\n </option>\n ))}\n </select>\n )}\n\n <div className=\"flex items-center gap-1 mt-2 flex-wrap\">\n {task.priority && (\n <span className={`px-1.5 py-0.5 text-[10px] rounded ${getPriorityColor(task.priority)}`}>\n {task.priority}\n </span>\n )}\n {task.assignee && (\n <span className=\"px-1.5 py-0.5 text-[10px] rounded bg-action-primary-bg/10 text-action-primary-text\">\n {task.assignee}\n </span>\n )}\n </div>\n </div>\n ))}\n\n {columnTasks.length === 0 && (\n <p className=\"text-[10px] text-text-muted text-center py-4\">\n {t('bigconsole.widget.kanban.noTasks', 'No tasks')}\n </p>\n )}\n </div>\n </div>\n );\n })}\n </div>\n </div>\n );\n});\n\nexport default KanbanView;\n"],"mappings":"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"}
1
+ {"version":3,"file":"KanbanView.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/kanban/KanbanView.tsx"],"sourcesContent":["/**\n * KanbanWidget Component\n *\n * Simple kanban board view with columns and tasks.\n */\n\nimport { type FC, memo, useMemo, useRef, useState } from 'react';\nimport {\n normalizeKanban,\n applyKanbanMoves,\n validKanbanMoves,\n type KanbanData,\n type KanbanTask,\n type KanbanColumnMove,\n} from './model';\nexport type { KanbanData, KanbanTask, KanbanColumn } from './model';\nimport type { Widget } from '../../../types';\nimport type { WidgetTranslateFn } from '../utils/translate';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface KanbanViewProps extends KanbanWidgetProps {\n /** Translator — the app passes useI18n().t; the public embed passes a static fallback translator. */\n t: WidgetTranslateFn;\n interactive?: boolean;\n onSaveMoves?: (moves: KanbanColumnMove[]) => Promise<boolean>;\n}\n\nexport interface KanbanWidgetProps {\n widget: Widget;\n data?: KanbanData;\n onClick?: (task: KanbanTask) => void;\n}\n\nfunction getPriorityColor(priority?: string): string {\n switch (priority) {\n case 'high':\n return 'bg-status-error-bg/20 text-status-error-text';\n case 'medium':\n return 'bg-status-warning-bg/20 text-[var(--color-status-warning-text)]';\n case 'low':\n return 'bg-status-success-bg/20 text-status-success-text';\n default:\n return 'bg-bg-sunken text-text-secondary';\n }\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const KanbanView: FC<KanbanViewProps> = memo(function KanbanView({\n widget,\n data,\n onClick,\n t,\n onSaveMoves,\n interactive = false,\n}) {\n const config = widget.config as Record<string, unknown> | undefined;\n const { columns, tasks: sourceTasks } = useMemo(() => normalizeKanban(data, config), [data, config]);\n const [local, setLocal] = useState<{ data: unknown; widgetId: string; moves: KanbanColumnMove[] } | null>(null);\n const [saving, setSaving] = useState(false);\n const [announcement, setAnnouncement] = useState('');\n const dragging = useRef<string | null>(null);\n const moves =\n local && local.data === data && local.widgetId === widget.id\n ? local.moves\n : validKanbanMoves(config?.kanbanColumnMoves);\n const tasks = useMemo(() => applyKanbanMoves(sourceTasks, columns, moves), [sourceTasks, columns, moves]);\n const moveTask = async (taskId: string, targetColumn: string) => {\n if (!interactive || saving || !columns.some((column) => column.id === targetColumn)) return;\n const task = sourceTasks.find((item) => item.id === taskId);\n if (!task || tasks.find((item) => item.id === taskId)?.column === targetColumn) return;\n const previous = validKanbanMoves(moves);\n const next = [\n ...previous.filter((move) => move.taskId !== taskId),\n { taskId, sourceColumn: task.column, targetColumn },\n ];\n setLocal({ data, widgetId: widget.id, moves: next });\n if (!onSaveMoves || !task.stableId) {\n setAnnouncement(\n t('bigconsole.widget.kanban.move.temporary', '{{title}} moved to {{column}} for this view.', {\n title: task.title,\n column: targetColumn,\n })\n );\n return;\n }\n setSaving(true);\n let saved: boolean;\n try {\n saved = await onSaveMoves(\n next.filter((move) => sourceTasks.some((source) => source.id === move.taskId && source.stableId))\n );\n } catch {\n saved = false;\n }\n setSaving(false);\n if (!saved) {\n setLocal({ data, widgetId: widget.id, moves: previous });\n setAnnouncement(\n t(\n 'bigconsole.widget.kanban.move.failed',\n 'Could not save the move. The card was returned to its previous column.'\n )\n );\n } else {\n setAnnouncement(\n t('bigconsole.widget.kanban.move.saved', '{{title}} moved to {{column}} and saved.', {\n title: task.title,\n column: targetColumn,\n })\n );\n }\n };\n\n // Group tasks by column\n const tasksByColumn = useMemo(() => {\n const grouped = new Map<string, KanbanTask[]>();\n columns.forEach((col) => {\n grouped.set(\n col.id,\n tasks.filter((t) => t.column === col.id)\n );\n });\n return grouped;\n }, [columns, tasks]);\n\n if (!columns.length) {\n return (\n <div className=\"flex items-center justify-center h-full text-text-secondary text-sm\">\n <div className=\"text-center\">\n <svg className=\"w-12 h-12 mx-auto mb-2 text-text-muted\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={1.5}\n d=\"M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a2 2 0 00-2 2\"\n />\n </svg>\n <p>{t('bigconsole.widget.kanban.emptyState', 'No board data')}</p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"flex h-full flex-col\">\n {interactive && (\n <p role=\"status\" className=\"px-2 pt-2 text-xs text-text-secondary\">\n {announcement ||\n (onSaveMoves\n ? t('bigconsole.widget.kanban.move.editorHint', 'Moves are saved to this widget.')\n : t('bigconsole.widget.kanban.move.viewerHint', 'Moves are temporary in this view.'))}\n </p>\n )}\n <div className=\"flex min-h-0 flex-1 p-2 gap-2 overflow-x-auto\">\n {columns.map((column) => {\n const columnTasks = tasksByColumn.get(column.id) || [];\n\n return (\n <div\n key={column.id}\n aria-label={`Column ${column.title}`}\n className=\"flex-shrink-0 w-48 flex flex-col bg-bg-sunken/50 rounded-lg\"\n onDragOver={(event) => {\n if (dragging.current && !saving) {\n // The dashboard's palette drop zone uses a copy effect; task moves must stay local.\n event.stopPropagation();\n event.preventDefault();\n event.dataTransfer.dropEffect = 'move';\n }\n }}\n onDrop={(event) => {\n event.preventDefault();\n event.stopPropagation();\n const id = dragging.current;\n dragging.current = null;\n if (id) void moveTask(id, column.id);\n }}\n >\n {/* Column Header */}\n <div className=\"flex items-center gap-2 p-2 border-b border-border-default\">\n <div\n className=\"w-2 h-2 rounded-full\"\n style={{ backgroundColor: column.color || 'var(--color-text-muted)' }}\n />\n <span className=\"text-xs font-medium text-text-primary truncate\">{column.title}</span>\n <span className=\"ml-auto text-xs text-text-muted\">{columnTasks.length}</span>\n </div>\n\n {/* Tasks */}\n <div className=\"flex-1 p-2 space-y-2 overflow-y-auto\">\n {columnTasks.map((task) => (\n <div\n key={task.id}\n draggable={interactive && !saving}\n onDragStart={(event) => {\n event.stopPropagation();\n dragging.current = task.id;\n event.dataTransfer.effectAllowed = 'move';\n event.dataTransfer.setData('text/plain', task.id);\n }}\n onDragEnd={() => {\n dragging.current = null;\n }}\n className={`\n p-2 bg-bg-surface rounded border border-border-default\n ${onClick ? 'cursor-pointer hover:border-action-primary-bg' : ''}\n transition-colors\n `}\n onClick={() => onClick?.(task)}\n >\n <p className=\"text-xs font-medium text-text-primary line-clamp-2\">{task.title}</p>\n {task.description && (\n <p className=\"mt-1 text-xs text-text-secondary line-clamp-3\">{task.description}</p>\n )}\n {interactive && (\n <select\n aria-label={`Move ${task.title} to column`}\n value={task.column}\n disabled={saving}\n onClick={(event) => event.stopPropagation()}\n onChange={(event) => {\n void moveTask(task.id, event.target.value);\n }}\n className=\"mt-2 w-full rounded border border-border-default bg-bg-surface text-xs text-text-secondary\"\n >\n {columns.map((target) => (\n <option key={target.id} value={target.id}>\n {target.title}\n </option>\n ))}\n </select>\n )}\n\n <div className=\"flex items-center gap-1 mt-2 flex-wrap\">\n {task.priority && (\n <span className={`px-1.5 py-0.5 text-[10px] rounded ${getPriorityColor(task.priority)}`}>\n {task.priority}\n </span>\n )}\n {task.assignee && (\n <span className=\"px-1.5 py-0.5 text-[10px] rounded bg-action-primary-bg/10 text-action-primary-text\">\n {task.assignee}\n </span>\n )}\n </div>\n </div>\n ))}\n\n {columnTasks.length === 0 && (\n <p className=\"text-[10px] text-text-muted text-center py-4\">\n {t('bigconsole.widget.kanban.noTasks', 'No tasks')}\n </p>\n )}\n </div>\n </div>\n );\n })}\n </div>\n </div>\n );\n});\n\nexport default KanbanView;\n"],"mappings":"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"}