@burdenoff/microfe-bigconsole 2026.925.2 → 2026.928.1

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 (47) hide show
  1. package/dist/bigconsole/components/embed/EmbedWidgetRenderer.js +43 -13
  2. package/dist/bigconsole/components/embed/EmbedWidgetRenderer.js.map +1 -1
  3. package/dist/bigconsole/components/embed/renderer-manifest.js +14 -47
  4. package/dist/bigconsole/components/embed/renderer-manifest.js.map +1 -1
  5. package/dist/bigconsole/components/embed/widgetShim.js +28 -0
  6. package/dist/bigconsole/components/embed/widgetShim.js.map +1 -0
  7. package/dist/bigconsole/components/embed/widgets/CalendarEmbed.js +38 -0
  8. package/dist/bigconsole/components/embed/widgets/CalendarEmbed.js.map +1 -0
  9. package/dist/bigconsole/components/embed/widgets/ChartEmbed.js +225 -0
  10. package/dist/bigconsole/components/embed/widgets/ChartEmbed.js.map +1 -0
  11. package/dist/bigconsole/components/embed/widgets/EmbedFrame.js +18 -0
  12. package/dist/bigconsole/components/embed/widgets/EmbedFrame.js.map +1 -0
  13. package/dist/bigconsole/components/embed/widgets/FormEmbed.js +147 -0
  14. package/dist/bigconsole/components/embed/widgets/FormEmbed.js.map +1 -0
  15. package/dist/bigconsole/components/embed/widgets/GaugeEmbed.js +44 -0
  16. package/dist/bigconsole/components/embed/widgets/GaugeEmbed.js.map +1 -0
  17. package/dist/bigconsole/components/embed/widgets/HeatmapEmbed.js +35 -0
  18. package/dist/bigconsole/components/embed/widgets/HeatmapEmbed.js.map +1 -0
  19. package/dist/bigconsole/components/embed/widgets/KanbanEmbed.js +50 -0
  20. package/dist/bigconsole/components/embed/widgets/KanbanEmbed.js.map +1 -0
  21. package/dist/bigconsole/components/embed/widgets/PivotEmbed.js +146 -0
  22. package/dist/bigconsole/components/embed/widgets/PivotEmbed.js.map +1 -0
  23. package/dist/bigconsole/components/embed/widgets/ProgressEmbed.js +30 -0
  24. package/dist/bigconsole/components/embed/widgets/ProgressEmbed.js.map +1 -0
  25. package/dist/bigconsole/components/embed/widgets/RetentionEmbed.js +41 -0
  26. package/dist/bigconsole/components/embed/widgets/RetentionEmbed.js.map +1 -0
  27. package/dist/bigconsole/components/embed/widgets/TimelineEmbed.js +40 -0
  28. package/dist/bigconsole/components/embed/widgets/TimelineEmbed.js.map +1 -0
  29. package/dist/bigconsole/components/embed/widgets/shared.js +13 -1
  30. package/dist/bigconsole/components/embed/widgets/shared.js.map +1 -1
  31. package/dist/bigconsole/components/widgets/calendar/CalendarView.js +152 -0
  32. package/dist/bigconsole/components/widgets/calendar/CalendarView.js.map +1 -0
  33. package/dist/bigconsole/components/widgets/calendar/CalendarWidget.js +10 -147
  34. package/dist/bigconsole/components/widgets/calendar/CalendarWidget.js.map +1 -1
  35. package/dist/bigconsole/components/widgets/chart/ChartWidget.js +19 -19
  36. package/dist/bigconsole/components/widgets/chart/index.js +8 -8
  37. package/dist/bigconsole/components/widgets/index.js +5 -5
  38. package/dist/bigconsole/components/widgets/kanban/KanbanView.js +192 -0
  39. package/dist/bigconsole/components/widgets/kanban/KanbanView.js.map +1 -0
  40. package/dist/bigconsole/components/widgets/kanban/KanbanWidget.js +10 -187
  41. package/dist/bigconsole/components/widgets/kanban/KanbanWidget.js.map +1 -1
  42. package/dist/bigconsole/components/widgets/metric-card/MetricCardWidget.js +20 -20
  43. package/dist/bigconsole/components/widgets/timeline/TimelineWidget.js +23 -22
  44. package/dist/bigconsole/components/widgets/timeline/TimelineWidget.js.map +1 -1
  45. package/dist/bigconsole/components/widgets/utils/translate.js +9 -0
  46. package/dist/bigconsole/components/widgets/utils/translate.js.map +1 -0
  47. package/package.json +1 -1
@@ -0,0 +1,147 @@
1
+ import { asFiniteNumber as e, asOptionalString as t, asRecord as n, asRecordArray as r, displayValue as i } from "./shared.js";
2
+ import { EmbedFrame as a } from "./EmbedFrame.js";
3
+ import { jsx as o, jsxs as s } from "react/jsx-runtime";
4
+ //#region src/bigconsole/components/embed/widgets/FormEmbed.tsx
5
+ var c = new Set([
6
+ "text",
7
+ "number",
8
+ "email",
9
+ "tel",
10
+ "url",
11
+ "date",
12
+ "datetime-local",
13
+ "time"
14
+ ]), l = new Set([
15
+ "hidden",
16
+ "password",
17
+ "file"
18
+ ]);
19
+ function u(e) {
20
+ return r(e, 100).map((e) => ({
21
+ value: i(e.value),
22
+ label: i(e.label) || i(e.value)
23
+ }));
24
+ }
25
+ function d({ widget: d }) {
26
+ let f = d.config, p = n(d.data), m = n(p.values), h = n(p.options), g = r(f.fields, 50).map((n) => ({
27
+ name: i(n.name),
28
+ label: i(n.label) || i(n.name),
29
+ type: i(n.type) || "text",
30
+ placeholder: t(n.placeholder, 120),
31
+ helpText: t(n.helpText, 300),
32
+ required: n.required === !0,
33
+ options: u(n.options),
34
+ rows: Math.min(Math.max(e(n.rows) ?? 4, 1), 20)
35
+ })).filter((e) => e.name !== "" && !l.has(e.type)), _ = f.layout === "grid" ? Math.min(Math.max(e(f.columns) ?? 2, 1), 4) : 1, v = t(f.submitLabel, 60) ?? "Submit", y = t(f.resetLabel, 60) ?? "Reset", b = "w-full rounded-md border border-border-default bg-bg-sunken px-3 py-2 text-sm text-text-primary opacity-80 cursor-not-allowed", x = (e) => {
36
+ let t = m[e.name], n = h[e.name] === void 0 ? e.options : u(h[e.name]);
37
+ switch (e.type) {
38
+ case "textarea": return /* @__PURE__ */ o("textarea", {
39
+ id: e.name,
40
+ className: b,
41
+ rows: e.rows,
42
+ value: i(t),
43
+ placeholder: e.placeholder,
44
+ disabled: !0,
45
+ readOnly: !0
46
+ });
47
+ case "select": return /* @__PURE__ */ s("select", {
48
+ id: e.name,
49
+ className: b,
50
+ value: i(t),
51
+ disabled: !0,
52
+ children: [/* @__PURE__ */ o("option", {
53
+ value: "",
54
+ children: e.placeholder ?? `Select ${e.label}…`
55
+ }), n.map((e) => /* @__PURE__ */ o("option", {
56
+ value: e.value,
57
+ children: e.label
58
+ }, e.value))]
59
+ });
60
+ case "checkbox": return /* @__PURE__ */ s("label", {
61
+ className: "flex items-center gap-2 text-sm text-text-primary",
62
+ children: [/* @__PURE__ */ o("input", {
63
+ type: "checkbox",
64
+ checked: t === !0,
65
+ disabled: !0,
66
+ readOnly: !0
67
+ }), e.label]
68
+ });
69
+ case "radio": return /* @__PURE__ */ o("div", {
70
+ className: "flex flex-col gap-2",
71
+ children: n.map((e) => /* @__PURE__ */ s("label", {
72
+ className: "flex items-center gap-2 text-sm text-text-primary",
73
+ children: [/* @__PURE__ */ o("input", {
74
+ type: "radio",
75
+ checked: i(t) === e.value,
76
+ disabled: !0,
77
+ readOnly: !0
78
+ }), e.label]
79
+ }, e.value))
80
+ });
81
+ default: return /* @__PURE__ */ o("input", {
82
+ id: e.name,
83
+ type: c.has(e.type) ? e.type : "text",
84
+ className: b,
85
+ value: i(t),
86
+ placeholder: e.placeholder,
87
+ disabled: !0,
88
+ readOnly: !0
89
+ });
90
+ }
91
+ };
92
+ return /* @__PURE__ */ o(a, {
93
+ title: d.title,
94
+ children: /* @__PURE__ */ s("div", {
95
+ className: "flex flex-col gap-4 p-4",
96
+ children: [
97
+ g.length === 0 ? /* @__PURE__ */ o("div", {
98
+ className: "text-sm text-text-tertiary",
99
+ children: "This form has no fields."
100
+ }) : /* @__PURE__ */ o("div", {
101
+ className: "grid gap-4",
102
+ style: { gridTemplateColumns: `repeat(${_}, minmax(0, 1fr))` },
103
+ children: g.map((e) => /* @__PURE__ */ s("div", {
104
+ className: "flex flex-col gap-1",
105
+ children: [
106
+ e.type !== "checkbox" && /* @__PURE__ */ s("label", {
107
+ htmlFor: e.name,
108
+ className: "text-sm font-medium text-text-primary",
109
+ children: [e.label, e.required && /* @__PURE__ */ o("span", {
110
+ className: "ml-0.5 text-status-error-text",
111
+ children: "*"
112
+ })]
113
+ }),
114
+ x(e),
115
+ e.helpText && /* @__PURE__ */ o("p", {
116
+ className: "text-xs text-text-tertiary",
117
+ children: e.helpText
118
+ })
119
+ ]
120
+ }, e.name))
121
+ }),
122
+ /* @__PURE__ */ s("div", {
123
+ className: "flex items-center gap-2",
124
+ children: [/* @__PURE__ */ o("button", {
125
+ type: "button",
126
+ disabled: !0,
127
+ className: "cursor-not-allowed rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text opacity-60",
128
+ children: v
129
+ }), f.showReset === !0 && /* @__PURE__ */ o("button", {
130
+ type: "button",
131
+ disabled: !0,
132
+ className: "cursor-not-allowed rounded-md border border-border-default px-4 py-2 text-sm text-text-secondary opacity-60",
133
+ children: y
134
+ })]
135
+ }),
136
+ /* @__PURE__ */ o("p", {
137
+ className: "text-xs text-text-tertiary",
138
+ children: "Interactive form disabled in embed."
139
+ })
140
+ ]
141
+ })
142
+ });
143
+ }
144
+ //#endregion
145
+ export { d as FormEmbed };
146
+
147
+ //# sourceMappingURL=FormEmbed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FormEmbed.js","names":[],"sources":["../../../../../src/bigconsole/components/embed/widgets/FormEmbed.tsx"],"sourcesContent":["/**\n * Read-only form embed renderer (BOFF-7926).\n *\n * Public embeds never accept anonymous submissions: there is no <form> element,\n * every control is `disabled`, the submit button is disabled, and a note says\n * so. The embed document CSP also sets `form-action 'none'`.\n *\n * Config (service whitelist): layout, columns, submitLabel, resetLabel,\n * showReset, fields[{name, label, type, placeholder, helpText, required,\n * options[{value, label}], rows}] — hidden/password fields and submitAction are\n * never sent (and are dropped here too). Data: `{ values: {name: scalar},\n * options: {name: [{value, label}]} }`.\n */\nimport type { EmbedWidgetRenderProps } from '../types';\nimport { EmbedFrame } from './EmbedFrame';\nimport { asFiniteNumber, asOptionalString, asRecord, asRecordArray, displayValue } from './shared';\n\nconst INPUT_TYPES = new Set(['text', 'number', 'email', 'tel', 'url', 'date', 'datetime-local', 'time']);\nconst EXCLUDED_TYPES = new Set(['hidden', 'password', 'file']);\n\ninterface EmbedFormOption {\n value: string;\n label: string;\n}\ninterface EmbedFormField {\n name: string;\n label: string;\n type: string;\n placeholder?: string;\n helpText?: string;\n required: boolean;\n options: EmbedFormOption[];\n rows: number;\n}\n\nfunction toOptions(value: unknown): EmbedFormOption[] {\n return asRecordArray(value, 100).map((o) => ({\n value: displayValue(o.value),\n label: displayValue(o.label) || displayValue(o.value),\n }));\n}\n\nexport function FormEmbed({ widget }: EmbedWidgetRenderProps) {\n const config = widget.config;\n const data = asRecord(widget.data);\n const values = asRecord(data.values);\n const dataOptions = asRecord(data.options);\n\n const fields: EmbedFormField[] = asRecordArray(config.fields, 50)\n .map((f) => ({\n name: displayValue(f.name),\n label: displayValue(f.label) || displayValue(f.name),\n type: displayValue(f.type) || 'text',\n placeholder: asOptionalString(f.placeholder, 120),\n helpText: asOptionalString(f.helpText, 300),\n required: f.required === true,\n options: toOptions(f.options),\n rows: Math.min(Math.max(asFiniteNumber(f.rows) ?? 4, 1), 20),\n }))\n .filter((f) => f.name !== '' && !EXCLUDED_TYPES.has(f.type));\n\n const gridColumns = config.layout === 'grid' ? Math.min(Math.max(asFiniteNumber(config.columns) ?? 2, 1), 4) : 1;\n const submitLabel = asOptionalString(config.submitLabel, 60) ?? 'Submit';\n const resetLabel = asOptionalString(config.resetLabel, 60) ?? 'Reset';\n const control =\n 'w-full rounded-md border border-border-default bg-bg-sunken px-3 py-2 text-sm text-text-primary opacity-80 cursor-not-allowed';\n\n const renderControl = (f: EmbedFormField) => {\n const value = values[f.name];\n const options = dataOptions[f.name] !== undefined ? toOptions(dataOptions[f.name]) : f.options;\n switch (f.type) {\n case 'textarea':\n return (\n <textarea\n id={f.name}\n className={control}\n rows={f.rows}\n value={displayValue(value)}\n placeholder={f.placeholder}\n disabled\n readOnly\n />\n );\n case 'select':\n return (\n <select id={f.name} className={control} value={displayValue(value)} disabled>\n <option value=\"\">{f.placeholder ?? `Select ${f.label}…`}</option>\n {options.map((o) => (\n <option key={o.value} value={o.value}>\n {o.label}\n </option>\n ))}\n </select>\n );\n case 'checkbox':\n return (\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input type=\"checkbox\" checked={value === true} disabled readOnly />\n {f.label}\n </label>\n );\n case 'radio':\n return (\n <div className=\"flex flex-col gap-2\">\n {options.map((o) => (\n <label key={o.value} className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input type=\"radio\" checked={displayValue(value) === o.value} disabled readOnly />\n {o.label}\n </label>\n ))}\n </div>\n );\n default:\n return (\n <input\n id={f.name}\n type={INPUT_TYPES.has(f.type) ? f.type : 'text'}\n className={control}\n value={displayValue(value)}\n placeholder={f.placeholder}\n disabled\n readOnly\n />\n );\n }\n };\n\n return (\n <EmbedFrame title={widget.title}>\n <div className=\"flex flex-col gap-4 p-4\">\n {fields.length === 0 ? (\n <div className=\"text-sm text-text-tertiary\">This form has no fields.</div>\n ) : (\n <div className=\"grid gap-4\" style={{ gridTemplateColumns: `repeat(${gridColumns}, minmax(0, 1fr))` }}>\n {fields.map((f) => (\n <div key={f.name} className=\"flex flex-col gap-1\">\n {f.type !== 'checkbox' && (\n <label htmlFor={f.name} className=\"text-sm font-medium text-text-primary\">\n {f.label}\n {f.required && <span className=\"ml-0.5 text-status-error-text\">*</span>}\n </label>\n )}\n {renderControl(f)}\n {f.helpText && <p className=\"text-xs text-text-tertiary\">{f.helpText}</p>}\n </div>\n ))}\n </div>\n )}\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n disabled\n className=\"cursor-not-allowed rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text opacity-60\"\n >\n {submitLabel}\n </button>\n {config.showReset === true && (\n <button\n type=\"button\"\n disabled\n className=\"cursor-not-allowed rounded-md border border-border-default px-4 py-2 text-sm text-text-secondary opacity-60\"\n >\n {resetLabel}\n </button>\n )}\n </div>\n <p className=\"text-xs text-text-tertiary\">Interactive form disabled in embed.</p>\n </div>\n </EmbedFrame>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ import { asArray as e, asFiniteNumber as t, asOptionalString as n, asRecord as r, safeColor as i } from "./shared.js";
2
+ import a from "../../widgets/gauge/GaugeWidget.js";
3
+ import { toWidgetShim as o } from "../widgetShim.js";
4
+ import { EmbedFrame as s } from "./EmbedFrame.js";
5
+ import { useMemo as c } from "react";
6
+ import { jsx as l } from "react/jsx-runtime";
7
+ //#region src/bigconsole/components/embed/widgets/GaugeEmbed.tsx
8
+ var u = /^[a-z][a-z-]{0,19}$/i;
9
+ function d(a) {
10
+ return e(a).slice(0, 10).map((e) => {
11
+ let a = r(e), o = t(a.value), s = typeof a.color == "string" ? a.color.trim() : "", c = u.test(s) ? s : i(s);
12
+ return o === null || !c ? null : {
13
+ value: o,
14
+ color: c,
15
+ label: n(a.label, 80)
16
+ };
17
+ }).filter((e) => e !== null);
18
+ }
19
+ function f({ widget: e }) {
20
+ let i = c(() => {
21
+ let t = o(e), n = d(e.config.thresholds);
22
+ return t.config = {
23
+ ...t.config,
24
+ thresholds: n.length > 0 ? n : void 0
25
+ }, t;
26
+ }, [e]), u = c(() => {
27
+ let i = r(e.data), a = t(i.value);
28
+ return a === null ? void 0 : {
29
+ value: a,
30
+ label: n(i.label, 120)
31
+ };
32
+ }, [e.data]);
33
+ return /* @__PURE__ */ l(s, {
34
+ title: e.title,
35
+ children: /* @__PURE__ */ l(a, {
36
+ widget: i,
37
+ data: u
38
+ })
39
+ });
40
+ }
41
+ //#endregion
42
+ export { f as GaugeEmbed };
43
+
44
+ //# sourceMappingURL=GaugeEmbed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GaugeEmbed.js","names":[],"sources":["../../../../../src/bigconsole/components/embed/widgets/GaugeEmbed.tsx"],"sourcesContent":["/**\n * Read-only gauge embed renderer (BOFF-7926).\n *\n * Reuses the in-app GaugeWidget for visual parity. The service projects the\n * final `{ value, label? }`; config is the presentation whitelist\n * (variant, min, max, unit(s), format, decimals, showZones, size, thresholds).\n */\nimport { useMemo } from 'react';\nimport { GaugeWidget, type GaugeData } from '../../widgets/gauge/GaugeWidget';\nimport type { EmbedWidgetRenderProps } from '../types';\nimport { toWidgetShim } from '../widgetShim';\nimport { EmbedFrame } from './EmbedFrame';\nimport { asArray, asFiniteNumber, asOptionalString, asRecord, safeColor } from './shared';\n\nconst THRESHOLD_WORD = /^[a-z][a-z-]{0,19}$/i;\n\n/** Thresholds: numeric value + a token word (success/warning/…) or an inert CSS color. */\nfunction sanitizeThresholds(value: unknown) {\n return asArray(value)\n .slice(0, 10)\n .map((t) => {\n const r = asRecord(t);\n const v = asFiniteNumber(r.value);\n const raw = typeof r.color === 'string' ? r.color.trim() : '';\n const color = THRESHOLD_WORD.test(raw) ? raw : safeColor(raw);\n return v === null || !color ? null : { value: v, color, label: asOptionalString(r.label, 80) };\n })\n .filter((t): t is { value: number; color: string; label: string | undefined } => t !== null);\n}\n\nexport function GaugeEmbed({ widget }: EmbedWidgetRenderProps) {\n const shim = useMemo(() => {\n const s = toWidgetShim(widget);\n const thresholds = sanitizeThresholds(widget.config.thresholds);\n s.config = { ...s.config, thresholds: thresholds.length > 0 ? thresholds : undefined };\n return s;\n }, [widget]);\n\n const data = useMemo((): GaugeData | undefined => {\n const d = asRecord(widget.data);\n const value = asFiniteNumber(d.value);\n return value === null ? undefined : { value, label: asOptionalString(d.label, 120) };\n }, [widget.data]);\n\n return (\n <EmbedFrame title={widget.title}>\n <GaugeWidget widget={shim} data={data} />\n </EmbedFrame>\n );\n}\n"],"mappings":";;;;;;;AAcA,IAAM,IAAiB;AAGvB,SAAS,EAAmB,GAAgB;AAC1C,QAAO,EAAQ,EAAM,CAClB,MAAM,GAAG,GAAG,CACZ,KAAK,MAAM;EACV,IAAM,IAAI,EAAS,EAAE,EACf,IAAI,EAAe,EAAE,MAAM,EAC3B,IAAM,OAAO,EAAE,SAAU,WAAW,EAAE,MAAM,MAAM,GAAG,IACrD,IAAQ,EAAe,KAAK,EAAI,GAAG,IAAM,EAAU,EAAI;AAC7D,SAAO,MAAM,QAAQ,CAAC,IAAQ,OAAO;GAAE,OAAO;GAAG;GAAO,OAAO,EAAiB,EAAE,OAAO,GAAG;GAAE;GAC9F,CACD,QAAQ,MAAwE,MAAM,KAAK;;AAGhG,SAAgB,EAAW,EAAE,aAAkC;CAC7D,IAAM,IAAO,QAAc;EACzB,IAAM,IAAI,EAAa,EAAO,EACxB,IAAa,EAAmB,EAAO,OAAO,WAAW;AAE/D,SADA,EAAE,SAAS;GAAE,GAAG,EAAE;GAAQ,YAAY,EAAW,SAAS,IAAI,IAAa,KAAA;GAAW,EAC/E;IACN,CAAC,EAAO,CAAC,EAEN,IAAO,QAAqC;EAChD,IAAM,IAAI,EAAS,EAAO,KAAK,EACzB,IAAQ,EAAe,EAAE,MAAM;AACrC,SAAO,MAAU,OAAO,KAAA,IAAY;GAAE;GAAO,OAAO,EAAiB,EAAE,OAAO,IAAI;GAAE;IACnF,CAAC,EAAO,KAAK,CAAC;AAEjB,QACE,kBAAC,GAAD;EAAY,OAAO,EAAO;YACxB,kBAAC,GAAD;GAAa,QAAQ;GAAY;GAAQ,CAAA;EAC9B,CAAA"}
@@ -0,0 +1,35 @@
1
+ import { asArray as e, asFiniteNumber as t, asRecord as n, asRecordArray as r, displayValue as i } from "./shared.js";
2
+ import { toWidgetShim as a } from "../widgetShim.js";
3
+ import { EmbedFrame as o } from "./EmbedFrame.js";
4
+ import s from "../../widgets/heatmap/HeatmapWidget.js";
5
+ import { useMemo as c } from "react";
6
+ import { jsx as l } from "react/jsx-runtime";
7
+ //#region src/bigconsole/components/embed/widgets/HeatmapEmbed.tsx
8
+ var u = 50, d = 2500;
9
+ function f({ widget: f }) {
10
+ let p = c(() => a(f), [f]), m = c(() => {
11
+ let a = n(f.data), o = e(a.rows).map(i).filter(Boolean).slice(0, u), s = e(a.columns).map(i).filter(Boolean).slice(0, u);
12
+ if (!(o.length === 0 || s.length === 0)) return {
13
+ rows: o,
14
+ columns: s,
15
+ cells: r(a.cells, d).map((e) => ({
16
+ row: i(e.row),
17
+ column: i(e.column),
18
+ value: t(e.value)
19
+ })).filter((e) => e.value !== null),
20
+ minValue: t(a.minValue) ?? void 0,
21
+ maxValue: t(a.maxValue) ?? void 0
22
+ };
23
+ }, [f.data]);
24
+ return /* @__PURE__ */ l(o, {
25
+ title: f.title,
26
+ children: /* @__PURE__ */ l(s, {
27
+ widget: p,
28
+ data: m
29
+ })
30
+ });
31
+ }
32
+ //#endregion
33
+ export { f as HeatmapEmbed };
34
+
35
+ //# sourceMappingURL=HeatmapEmbed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HeatmapEmbed.js","names":[],"sources":["../../../../../src/bigconsole/components/embed/widgets/HeatmapEmbed.tsx"],"sourcesContent":["/**\n * Read-only heatmap embed renderer (BOFF-7926).\n *\n * Reuses the in-app HeatmapWidget. The service projects\n * `{ rows: string[], columns: string[], cells: [{row, column, value}], minValue?, maxValue? }`.\n */\nimport { useMemo } from 'react';\nimport { HeatmapWidget, type HeatmapData } from '../../widgets/heatmap/HeatmapWidget';\nimport type { EmbedWidgetRenderProps } from '../types';\nimport { toWidgetShim } from '../widgetShim';\nimport { EmbedFrame } from './EmbedFrame';\nimport { asArray, asFiniteNumber, asRecord, asRecordArray, displayValue } from './shared';\n\nconst MAX_AXIS = 50;\nconst MAX_CELLS = 2500;\n\nexport function HeatmapEmbed({ widget }: EmbedWidgetRenderProps) {\n const shim = useMemo(() => toWidgetShim(widget), [widget]);\n const data = useMemo((): HeatmapData | undefined => {\n const d = asRecord(widget.data);\n const rows = asArray(d.rows).map(displayValue).filter(Boolean).slice(0, MAX_AXIS);\n const columns = asArray(d.columns).map(displayValue).filter(Boolean).slice(0, MAX_AXIS);\n if (rows.length === 0 || columns.length === 0) return undefined;\n const cells = asRecordArray(d.cells, MAX_CELLS)\n .map((c) => ({ row: displayValue(c.row), column: displayValue(c.column), value: asFiniteNumber(c.value) }))\n .filter((c): c is { row: string; column: string; value: number } => c.value !== null);\n return {\n rows,\n columns,\n cells,\n minValue: asFiniteNumber(d.minValue) ?? undefined,\n maxValue: asFiniteNumber(d.maxValue) ?? undefined,\n };\n }, [widget.data]);\n return (\n <EmbedFrame title={widget.title}>\n <HeatmapWidget widget={shim} data={data} />\n </EmbedFrame>\n );\n}\n"],"mappings":";;;;;;;AAaA,IAAM,IAAW,IACX,IAAY;AAElB,SAAgB,EAAa,EAAE,aAAkC;CAC/D,IAAM,IAAO,QAAc,EAAa,EAAO,EAAE,CAAC,EAAO,CAAC,EACpD,IAAO,QAAuC;EAClD,IAAM,IAAI,EAAS,EAAO,KAAK,EACzB,IAAO,EAAQ,EAAE,KAAK,CAAC,IAAI,EAAa,CAAC,OAAO,QAAQ,CAAC,MAAM,GAAG,EAAS,EAC3E,IAAU,EAAQ,EAAE,QAAQ,CAAC,IAAI,EAAa,CAAC,OAAO,QAAQ,CAAC,MAAM,GAAG,EAAS;AACnF,UAAK,WAAW,KAAK,EAAQ,WAAW,GAI5C,QAAO;GACL;GACA;GACA,OANY,EAAc,EAAE,OAAO,EAAU,CAC5C,KAAK,OAAO;IAAE,KAAK,EAAa,EAAE,IAAI;IAAE,QAAQ,EAAa,EAAE,OAAO;IAAE,OAAO,EAAe,EAAE,MAAM;IAAE,EAAE,CAC1G,QAAQ,MAA2D,EAAE,UAAU,KAAK;GAKrF,UAAU,EAAe,EAAE,SAAS,IAAI,KAAA;GACxC,UAAU,EAAe,EAAE,SAAS,IAAI,KAAA;GACzC;IACA,CAAC,EAAO,KAAK,CAAC;AACjB,QACE,kBAAC,GAAD;EAAY,OAAO,EAAO;YACxB,kBAAC,GAAD;GAAe,QAAQ;GAAY;GAAQ,CAAA;EAChC,CAAA"}
@@ -0,0 +1,50 @@
1
+ import { asArray as e, asRecord as t, asRecordArray as n, displayValue as r, safeColor as i } from "./shared.js";
2
+ import { toWidgetShim as a } from "../widgetShim.js";
3
+ import { EmbedFrame as o } from "./EmbedFrame.js";
4
+ import { fallbackTranslate as s } from "../../widgets/utils/translate.js";
5
+ import c from "../../widgets/kanban/KanbanView.js";
6
+ import { useMemo as l } from "react";
7
+ import { jsx as u } from "react/jsx-runtime";
8
+ //#region src/bigconsole/components/embed/widgets/KanbanEmbed.tsx
9
+ var d = 20, f = 1e3, p = new Set([
10
+ "high",
11
+ "medium",
12
+ "low"
13
+ ]);
14
+ function m({ widget: m }) {
15
+ let h = l(() => ({
16
+ ...a(m),
17
+ config: {}
18
+ }), [m]), g = l(() => {
19
+ let a = t(m.data);
20
+ return {
21
+ columns: n(a.columns, d).map((e, t) => ({
22
+ id: r(e.id) || `c${t}`,
23
+ title: r(e.title) || r(e.id),
24
+ color: i(e.color)
25
+ })).filter((e) => e.title !== ""),
26
+ tasks: n(a.tasks, f).map((t, n) => {
27
+ let i = typeof t.priority == "string" && p.has(t.priority) ? t.priority : void 0, a = e(t.labels).filter((e) => typeof e == "string").slice(0, 10);
28
+ return {
29
+ id: `t${n}`,
30
+ title: r(t.title),
31
+ column: r(t.column),
32
+ priority: i,
33
+ labels: a.length > 0 ? a : void 0
34
+ };
35
+ })
36
+ };
37
+ }, [m.data]);
38
+ return /* @__PURE__ */ u(o, {
39
+ title: m.title,
40
+ children: /* @__PURE__ */ u(c, {
41
+ widget: h,
42
+ data: g,
43
+ t: s
44
+ })
45
+ });
46
+ }
47
+ //#endregion
48
+ export { m as KanbanEmbed };
49
+
50
+ //# sourceMappingURL=KanbanEmbed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KanbanEmbed.js","names":[],"sources":["../../../../../src/bigconsole/components/embed/widgets/KanbanEmbed.tsx"],"sourcesContent":["/**\n * Read-only kanban embed renderer (BOFF-7926).\n *\n * Reuses the provider-free KanbanView with a static translator. No drag, no\n * click. The service projects `{ columns: [{id, title}], tasks: [{title, column,\n * priority?, labels?}] }` and never sends `assignee` (people's names/emails stay\n * out of public embeds) — the renderer drops it too, defensively.\n */\nimport { useMemo } from 'react';\nimport { KanbanView, type KanbanData, type KanbanTask } from '../../widgets/kanban/KanbanView';\nimport { fallbackTranslate } from '../../widgets/utils/translate';\nimport type { EmbedWidgetRenderProps } from '../types';\nimport { toWidgetShim } from '../widgetShim';\nimport { EmbedFrame } from './EmbedFrame';\nimport { asArray, asRecord, asRecordArray, displayValue, safeColor } from './shared';\n\nconst MAX_COLUMNS = 20;\nconst MAX_TASKS = 1000;\nconst PRIORITIES = new Set(['high', 'medium', 'low']);\n\nexport function KanbanEmbed({ widget }: EmbedWidgetRenderProps) {\n // The view's row-normalisation reads field-mapping config; the service already\n // produced the canonical board, so pass only the title through.\n const shim = useMemo(() => ({ ...toWidgetShim(widget), config: {} }), [widget]);\n const data = useMemo((): KanbanData => {\n const d = asRecord(widget.data);\n const columns = asRecordArray(d.columns, MAX_COLUMNS)\n .map((c, i) => ({\n id: displayValue(c.id) || `c${i}`,\n title: displayValue(c.title) || displayValue(c.id),\n color: safeColor(c.color),\n }))\n .filter((c) => c.title !== '');\n const tasks: KanbanTask[] = asRecordArray(d.tasks, MAX_TASKS).map((t, i) => {\n const priority = typeof t.priority === 'string' && PRIORITIES.has(t.priority) ? t.priority : undefined;\n const labels = asArray(t.labels)\n .filter((l): l is string => typeof l === 'string')\n .slice(0, 10);\n return {\n id: `t${i}`,\n title: displayValue(t.title),\n column: displayValue(t.column),\n priority: priority as KanbanTask['priority'],\n labels: labels.length > 0 ? labels : undefined,\n };\n });\n return { columns, tasks };\n }, [widget.data]);\n return (\n <EmbedFrame title={widget.title}>\n <KanbanView widget={shim} data={data} t={fallbackTranslate} />\n </EmbedFrame>\n );\n}\n"],"mappings":";;;;;;;;AAgBA,IAAM,IAAc,IACd,IAAY,KACZ,IAAa,IAAI,IAAI;CAAC;CAAQ;CAAU;CAAM,CAAC;AAErD,SAAgB,EAAY,EAAE,aAAkC;CAG9D,IAAM,IAAO,SAAe;EAAE,GAAG,EAAa,EAAO;EAAE,QAAQ,EAAE;EAAE,GAAG,CAAC,EAAO,CAAC,EACzE,IAAO,QAA0B;EACrC,IAAM,IAAI,EAAS,EAAO,KAAK;AAqB/B,SAAO;GAAE,SApBO,EAAc,EAAE,SAAS,EAAY,CAClD,KAAK,GAAG,OAAO;IACd,IAAI,EAAa,EAAE,GAAG,IAAI,IAAI;IAC9B,OAAO,EAAa,EAAE,MAAM,IAAI,EAAa,EAAE,GAAG;IAClD,OAAO,EAAU,EAAE,MAAM;IAC1B,EAAE,CACF,QAAQ,MAAM,EAAE,UAAU,GAAG;GAcd,OAbU,EAAc,EAAE,OAAO,EAAU,CAAC,KAAK,GAAG,MAAM;IAC1E,IAAM,IAAW,OAAO,EAAE,YAAa,YAAY,EAAW,IAAI,EAAE,SAAS,GAAG,EAAE,WAAW,KAAA,GACvF,IAAS,EAAQ,EAAE,OAAO,CAC7B,QAAQ,MAAmB,OAAO,KAAM,SAAS,CACjD,MAAM,GAAG,GAAG;AACf,WAAO;KACL,IAAI,IAAI;KACR,OAAO,EAAa,EAAE,MAAM;KAC5B,QAAQ,EAAa,EAAE,OAAO;KACpB;KACV,QAAQ,EAAO,SAAS,IAAI,IAAS,KAAA;KACtC;KACD;GACuB;IACxB,CAAC,EAAO,KAAK,CAAC;AACjB,QACE,kBAAC,GAAD;EAAY,OAAO,EAAO;YACxB,kBAAC,GAAD;GAAY,QAAQ;GAAY;GAAM,GAAG;GAAqB,CAAA;EACnD,CAAA"}
@@ -0,0 +1,146 @@
1
+ import { asArray as e, asFiniteNumber as t, asOptionalString as n, asRecord as r, asRecordArray as i, displayValue as a, formatNumber as o } from "./shared.js";
2
+ import { EmbedFrame as s } from "./EmbedFrame.js";
3
+ import { useCallback as c, useMemo as l, useState as u } from "react";
4
+ import { jsx as d, jsxs as f } from "react/jsx-runtime";
5
+ //#region src/bigconsole/components/embed/widgets/PivotEmbed.tsx
6
+ var p = 50, m = 2e3;
7
+ function h(e, n, i) {
8
+ if (i) {
9
+ let n = t(e);
10
+ if (n !== null) return n;
11
+ }
12
+ return t(r(e)[n]);
13
+ }
14
+ function g({ widget: t }) {
15
+ let g = t.config, _ = l(() => ({
16
+ format: n(g.numberFormat, 20) ?? "number",
17
+ decimals: g.decimalPlaces
18
+ }), [g.numberFormat, g.decimalPlaces]), v = g.showRowTotals !== !1, y = g.showColumnTotals !== !1, b = g.showGrandTotal !== !1, x = g.expandable !== !1, S = l(() => {
19
+ let o = r(t.data), s = typeof o.columnField == "string" && o.columnField.length > 0 ? e(o.columnKeys).map(a).slice(0, p) : [], c = e(o.rowFields).map(a).filter(Boolean).slice(0, 10).join(" / "), l = i(o.valueFields, 10).map((e) => {
20
+ let t = a(e.key), r = n(e.aggregation, 20);
21
+ return {
22
+ key: t,
23
+ label: n(e.label, 80) ?? (r ? `${r}(${t})` : t)
24
+ };
25
+ }).filter((e) => e.key !== ""), u = i(o.nodes, m).map((t) => {
26
+ let n = e(t.path).map(a).slice(0, 10);
27
+ return {
28
+ path: n,
29
+ key: n.join("\0"),
30
+ byColumn: r(t.byColumn),
31
+ total: t.total
32
+ };
33
+ }).filter((e) => e.path.length > 0), d = /* @__PURE__ */ new Set();
34
+ for (let e of u) for (let t = 1; t < e.path.length; t++) d.add(e.path.slice(0, t).join("\0"));
35
+ return {
36
+ columnKeys: s,
37
+ rowHeader: c,
38
+ valueFields: l,
39
+ nodes: u,
40
+ parentKeys: d,
41
+ columnTotals: r(o.columnTotals),
42
+ grandTotal: o.grandTotal
43
+ };
44
+ }, [t.data]), [C, w] = u(/* @__PURE__ */ new Set()), T = c((e) => {
45
+ w((t) => {
46
+ let n = new Set(t);
47
+ return n.has(e) ? n.delete(e) : n.add(e), n;
48
+ });
49
+ }, []), { columnKeys: E, rowHeader: D, valueFields: O, nodes: k, parentKeys: A, columnTotals: j, grandTotal: M } = S;
50
+ if (k.length === 0 || O.length === 0) return /* @__PURE__ */ d(s, {
51
+ title: t.title,
52
+ children: /* @__PURE__ */ d("div", {
53
+ className: "flex h-full items-center justify-center p-4 text-sm text-text-tertiary",
54
+ children: "No data"
55
+ })
56
+ });
57
+ let N = O.length === 1, P = E.length > 0, F = (e) => e === null ? "" : o(e, _), I = (e) => e.path.some((t, n) => n < e.path.length - 1 && C.has(e.path.slice(0, n + 1).join("\0"))), L = k.filter((e) => !I(e)), R = "border-b border-border-subtle px-3 py-1.5 text-right tabular-nums text-text-primary", z = "border-b border-border-default bg-bg-sunken px-3 py-2 text-right text-xs font-medium text-text-secondary";
58
+ return /* @__PURE__ */ d(s, {
59
+ title: t.title,
60
+ children: /* @__PURE__ */ f("table", {
61
+ className: "w-full border-collapse text-sm",
62
+ children: [
63
+ /* @__PURE__ */ d("thead", {
64
+ className: "sticky top-0",
65
+ children: /* @__PURE__ */ f("tr", { children: [
66
+ /* @__PURE__ */ d("th", {
67
+ className: `${z} text-left`,
68
+ children: D
69
+ }),
70
+ P ? E.flatMap((e) => O.map((t) => /* @__PURE__ */ d("th", {
71
+ className: z,
72
+ children: N ? e : `${e} · ${t.label}`
73
+ }, `${e}|${t.key}`))) : O.map((e) => /* @__PURE__ */ d("th", {
74
+ className: z,
75
+ children: e.label
76
+ }, e.key)),
77
+ P && v && O.map((e) => /* @__PURE__ */ d("th", {
78
+ className: z,
79
+ children: N ? "Total" : `Total · ${e.label}`
80
+ }, `total|${e.key}`))
81
+ ] })
82
+ }),
83
+ /* @__PURE__ */ d("tbody", { children: L.map((e) => {
84
+ let t = e.path.length - 1, n = A.has(e.key), r = !C.has(e.key);
85
+ return /* @__PURE__ */ f("tr", {
86
+ className: n ? "bg-bg-sunken/40" : void 0,
87
+ children: [
88
+ /* @__PURE__ */ d("td", {
89
+ className: "border-b border-border-subtle px-3 py-1.5 text-left text-text-primary",
90
+ children: /* @__PURE__ */ f("span", {
91
+ className: "inline-flex items-center gap-1",
92
+ style: { paddingLeft: `${t * 16}px` },
93
+ children: [n && x ? /* @__PURE__ */ d("button", {
94
+ type: "button",
95
+ onClick: () => T(e.key),
96
+ "aria-expanded": r,
97
+ className: "w-4 text-text-tertiary hover:text-text-primary",
98
+ children: r ? "▾" : "▸"
99
+ }) : /* @__PURE__ */ d("span", { className: "w-4" }), /* @__PURE__ */ d("span", {
100
+ className: n ? "font-medium" : void 0,
101
+ children: e.path[e.path.length - 1]
102
+ })]
103
+ })
104
+ }),
105
+ P ? E.flatMap((t) => O.map((n) => /* @__PURE__ */ d("td", {
106
+ className: R,
107
+ children: F(h(e.byColumn[t], n.key, N))
108
+ }, `${t}|${n.key}`))) : O.map((t) => /* @__PURE__ */ d("td", {
109
+ className: R,
110
+ children: F(h(e.total, t.key, N))
111
+ }, t.key)),
112
+ P && v && O.map((t) => /* @__PURE__ */ d("td", {
113
+ className: `${R} font-medium`,
114
+ children: F(h(e.total, t.key, N))
115
+ }, `total|${t.key}`))
116
+ ]
117
+ }, e.key);
118
+ }) }),
119
+ (y || b) && /* @__PURE__ */ d("tfoot", { children: /* @__PURE__ */ f("tr", {
120
+ className: "bg-bg-sunken font-medium",
121
+ children: [
122
+ /* @__PURE__ */ d("td", {
123
+ className: "px-3 py-2 text-left text-text-primary",
124
+ children: "Total"
125
+ }),
126
+ P ? E.flatMap((e) => O.map((t) => /* @__PURE__ */ d("td", {
127
+ className: "px-3 py-2 text-right tabular-nums text-text-primary",
128
+ children: y ? F(h(j[e], t.key, N)) : ""
129
+ }, `${e}|${t.key}`))) : O.map((e) => /* @__PURE__ */ d("td", {
130
+ className: "px-3 py-2 text-right tabular-nums text-text-primary",
131
+ children: b ? F(h(M, e.key, N)) : ""
132
+ }, e.key)),
133
+ P && v && O.map((e) => /* @__PURE__ */ d("td", {
134
+ className: "px-3 py-2 text-right tabular-nums text-text-primary",
135
+ children: b ? F(h(M, e.key, N)) : ""
136
+ }, `total|${e.key}`))
137
+ ]
138
+ }) })
139
+ ]
140
+ })
141
+ });
142
+ }
143
+ //#endregion
144
+ export { g as PivotEmbed };
145
+
146
+ //# sourceMappingURL=PivotEmbed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PivotEmbed.js","names":[],"sources":["../../../../../src/bigconsole/components/embed/widgets/PivotEmbed.tsx"],"sourcesContent":["/**\n * Read-only pivot table embed renderer (BOFF-7926).\n *\n * The service aggregates SERVER-SIDE and sends only the aggregated tree — raw\n * rows never reach a public embed (the in-app pivot's field shelf lists every\n * key of the raw rows). Shape:\n *\n * {\n * rowFields: string[],\n * columnField: string | null,\n * columnKeys: string[], // ['Total'] when columnField is null\n * valueFields: [{ key, label?, aggregation? }],\n * nodes: [{ path: string[], byColumn?: {col: {vf: n}}, total?: {vf: n} | n }],\n * columnTotals?: {col: {vf: n}},\n * grandTotal?: {vf: n} | n,\n * }\n *\n * Config: showRowTotals, showColumnTotals, showGrandTotal, expandable,\n * numberFormat, decimalPlaces. Expand/collapse is local UI state only.\n */\nimport { useCallback, useMemo, useState } from 'react';\nimport type { EmbedWidgetRenderProps } from '../types';\nimport { EmbedFrame } from './EmbedFrame';\nimport {\n asArray,\n asFiniteNumber,\n asOptionalString,\n asRecord,\n asRecordArray,\n displayValue,\n formatNumber,\n} from './shared';\n\nconst MAX_COLUMNS = 50;\nconst MAX_NODES = 2000;\n\ninterface PivotValueField {\n key: string;\n label: string;\n}\ninterface PivotNode {\n path: string[];\n key: string;\n byColumn: Record<string, unknown>;\n total: unknown;\n}\n\n/** Read `obj[col][vf]` (or `obj[vf]` / a bare number when there is one value field). */\nfunction pick(source: unknown, vf: string, single: boolean): number | null {\n if (single) {\n const direct = asFiniteNumber(source);\n if (direct !== null) return direct;\n }\n return asFiniteNumber(asRecord(source)[vf]);\n}\n\nexport function PivotEmbed({ widget }: EmbedWidgetRenderProps) {\n const config = widget.config;\n const numberConfig = useMemo(\n () => ({ format: asOptionalString(config.numberFormat, 20) ?? 'number', decimals: config.decimalPlaces }),\n [config.numberFormat, config.decimalPlaces]\n );\n const showRowTotals = config.showRowTotals !== false;\n const showColumnTotals = config.showColumnTotals !== false;\n const showGrandTotal = config.showGrandTotal !== false;\n const expandable = config.expandable !== false;\n\n const model = useMemo(() => {\n const d = asRecord(widget.data);\n // No column field: the service still sends `columnKeys: ['Total']`; render\n // the value fields directly from each node's `total` instead.\n const hasColumnField = typeof d.columnField === 'string' && d.columnField.length > 0;\n const columnKeys = hasColumnField ? asArray(d.columnKeys).map(displayValue).slice(0, MAX_COLUMNS) : [];\n const rowHeader = asArray(d.rowFields).map(displayValue).filter(Boolean).slice(0, 10).join(' / ');\n const valueFields: PivotValueField[] = asRecordArray(d.valueFields, 10)\n .map((v) => {\n const key = displayValue(v.key);\n const agg = asOptionalString(v.aggregation, 20);\n const label = asOptionalString(v.label, 80) ?? (agg ? `${agg}(${key})` : key);\n return { key, label };\n })\n .filter((v) => v.key !== '');\n const nodes: PivotNode[] = asRecordArray(d.nodes, MAX_NODES)\n .map((n) => {\n const path = asArray(n.path).map(displayValue).slice(0, 10);\n return { path, key: path.join('\\u0000'), byColumn: asRecord(n.byColumn), total: n.total };\n })\n .filter((n) => n.path.length > 0);\n const parentKeys = new Set<string>();\n for (const n of nodes) {\n for (let i = 1; i < n.path.length; i++) parentKeys.add(n.path.slice(0, i).join('\\u0000'));\n }\n return {\n columnKeys,\n rowHeader,\n valueFields,\n nodes,\n parentKeys,\n columnTotals: asRecord(d.columnTotals),\n grandTotal: d.grandTotal,\n };\n }, [widget.data]);\n\n const [collapsed, setCollapsed] = useState<ReadonlySet<string>>(new Set());\n const toggle = useCallback((key: string) => {\n setCollapsed((prev) => {\n const next = new Set(prev);\n if (next.has(key)) next.delete(key);\n else next.add(key);\n return next;\n });\n }, []);\n\n const { columnKeys, rowHeader, valueFields, nodes, parentKeys, columnTotals, grandTotal } = model;\n if (nodes.length === 0 || valueFields.length === 0) {\n return (\n <EmbedFrame title={widget.title}>\n <div className=\"flex h-full items-center justify-center p-4 text-sm text-text-tertiary\">No data</div>\n </EmbedFrame>\n );\n }\n\n const single = valueFields.length === 1;\n const hasColumns = columnKeys.length > 0;\n const fmt = (v: number | null) => (v === null ? '' : formatNumber(v, numberConfig));\n const isHidden = (n: PivotNode) =>\n n.path.some((_, i) => i < n.path.length - 1 && collapsed.has(n.path.slice(0, i + 1).join('\\u0000')));\n const visible = nodes.filter((n) => !isHidden(n));\n\n const cellClass = 'border-b border-border-subtle px-3 py-1.5 text-right tabular-nums text-text-primary';\n const headClass =\n 'border-b border-border-default bg-bg-sunken px-3 py-2 text-right text-xs font-medium text-text-secondary';\n\n return (\n <EmbedFrame title={widget.title}>\n <table className=\"w-full border-collapse text-sm\">\n <thead className=\"sticky top-0\">\n <tr>\n <th className={`${headClass} text-left`}>{rowHeader}</th>\n {hasColumns\n ? columnKeys.flatMap((col) =>\n valueFields.map((vf) => (\n <th key={`${col}|${vf.key}`} className={headClass}>\n {single ? col : `${col} · ${vf.label}`}\n </th>\n ))\n )\n : valueFields.map((vf) => (\n <th key={vf.key} className={headClass}>\n {vf.label}\n </th>\n ))}\n {hasColumns &&\n showRowTotals &&\n valueFields.map((vf) => (\n <th key={`total|${vf.key}`} className={headClass}>\n {single ? 'Total' : `Total · ${vf.label}`}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {visible.map((n) => {\n const depth = n.path.length - 1;\n const isParent = parentKeys.has(n.key);\n const open = !collapsed.has(n.key);\n return (\n <tr key={n.key} className={isParent ? 'bg-bg-sunken/40' : undefined}>\n <td className=\"border-b border-border-subtle px-3 py-1.5 text-left text-text-primary\">\n <span className=\"inline-flex items-center gap-1\" style={{ paddingLeft: `${depth * 16}px` }}>\n {isParent && expandable ? (\n <button\n type=\"button\"\n onClick={() => toggle(n.key)}\n aria-expanded={open}\n className=\"w-4 text-text-tertiary hover:text-text-primary\"\n >\n {open ? '▾' : '▸'}\n </button>\n ) : (\n <span className=\"w-4\" />\n )}\n <span className={isParent ? 'font-medium' : undefined}>{n.path[n.path.length - 1]}</span>\n </span>\n </td>\n {hasColumns\n ? columnKeys.flatMap((col) =>\n valueFields.map((vf) => (\n <td key={`${col}|${vf.key}`} className={cellClass}>\n {fmt(pick(n.byColumn[col], vf.key, single))}\n </td>\n ))\n )\n : valueFields.map((vf) => (\n <td key={vf.key} className={cellClass}>\n {fmt(pick(n.total, vf.key, single))}\n </td>\n ))}\n {hasColumns &&\n showRowTotals &&\n valueFields.map((vf) => (\n <td key={`total|${vf.key}`} className={`${cellClass} font-medium`}>\n {fmt(pick(n.total, vf.key, single))}\n </td>\n ))}\n </tr>\n );\n })}\n </tbody>\n {(showColumnTotals || showGrandTotal) && (\n <tfoot>\n <tr className=\"bg-bg-sunken font-medium\">\n <td className=\"px-3 py-2 text-left text-text-primary\">Total</td>\n {hasColumns\n ? columnKeys.flatMap((col) =>\n valueFields.map((vf) => (\n <td key={`${col}|${vf.key}`} className=\"px-3 py-2 text-right tabular-nums text-text-primary\">\n {showColumnTotals ? fmt(pick(columnTotals[col], vf.key, single)) : ''}\n </td>\n ))\n )\n : valueFields.map((vf) => (\n <td key={vf.key} className=\"px-3 py-2 text-right tabular-nums text-text-primary\">\n {showGrandTotal ? fmt(pick(grandTotal, vf.key, single)) : ''}\n </td>\n ))}\n {hasColumns &&\n showRowTotals &&\n valueFields.map((vf) => (\n <td key={`total|${vf.key}`} className=\"px-3 py-2 text-right tabular-nums text-text-primary\">\n {showGrandTotal ? fmt(pick(grandTotal, vf.key, single)) : ''}\n </td>\n ))}\n </tr>\n </tfoot>\n )}\n </table>\n </EmbedFrame>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import { asFiniteNumber as e, asOptionalString as t, asRecord as n } from "./shared.js";
2
+ import { toWidgetShim as r } from "../widgetShim.js";
3
+ import { EmbedFrame as i } from "./EmbedFrame.js";
4
+ import a from "../../widgets/progress/ProgressWidget.js";
5
+ import { useMemo as o } from "react";
6
+ import { jsx as s } from "react/jsx-runtime";
7
+ //#region src/bigconsole/components/embed/widgets/ProgressEmbed.tsx
8
+ function c({ widget: c }) {
9
+ let l = o(() => r(c), [c]), u = o(() => {
10
+ let r = n(c.data), i = e(r.value);
11
+ if (i !== null) return {
12
+ value: i,
13
+ max: e(r.max) ?? void 0,
14
+ target: e(r.target) ?? void 0,
15
+ label: t(r.label, 120),
16
+ description: t(r.description, 300)
17
+ };
18
+ }, [c.data]);
19
+ return /* @__PURE__ */ s(i, {
20
+ title: c.title,
21
+ children: /* @__PURE__ */ s(a, {
22
+ widget: l,
23
+ data: u
24
+ })
25
+ });
26
+ }
27
+ //#endregion
28
+ export { c as ProgressEmbed };
29
+
30
+ //# sourceMappingURL=ProgressEmbed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProgressEmbed.js","names":[],"sources":["../../../../../src/bigconsole/components/embed/widgets/ProgressEmbed.tsx"],"sourcesContent":["/**\n * Read-only progress embed renderer (BOFF-7926).\n *\n * Reuses the in-app ProgressWidget. The service projects the final\n * `{ value, max?, target?, label?, description? }`.\n */\nimport { useMemo } from 'react';\nimport { ProgressWidget, type ProgressData } from '../../widgets/progress/ProgressWidget';\nimport type { EmbedWidgetRenderProps } from '../types';\nimport { toWidgetShim } from '../widgetShim';\nimport { EmbedFrame } from './EmbedFrame';\nimport { asFiniteNumber, asOptionalString, asRecord } from './shared';\n\nexport function ProgressEmbed({ widget }: EmbedWidgetRenderProps) {\n const shim = useMemo(() => toWidgetShim(widget), [widget]);\n const data = useMemo((): ProgressData | undefined => {\n const d = asRecord(widget.data);\n const value = asFiniteNumber(d.value);\n if (value === null) return undefined;\n return {\n value,\n max: asFiniteNumber(d.max) ?? undefined,\n target: asFiniteNumber(d.target) ?? undefined,\n label: asOptionalString(d.label, 120),\n description: asOptionalString(d.description, 300),\n };\n }, [widget.data]);\n return (\n <EmbedFrame title={widget.title}>\n <ProgressWidget widget={shim} data={data} />\n </EmbedFrame>\n );\n}\n"],"mappings":";;;;;;;AAaA,SAAgB,EAAc,EAAE,aAAkC;CAChE,IAAM,IAAO,QAAc,EAAa,EAAO,EAAE,CAAC,EAAO,CAAC,EACpD,IAAO,QAAwC;EACnD,IAAM,IAAI,EAAS,EAAO,KAAK,EACzB,IAAQ,EAAe,EAAE,MAAM;AACjC,YAAU,KACd,QAAO;GACL;GACA,KAAK,EAAe,EAAE,IAAI,IAAI,KAAA;GAC9B,QAAQ,EAAe,EAAE,OAAO,IAAI,KAAA;GACpC,OAAO,EAAiB,EAAE,OAAO,IAAI;GACrC,aAAa,EAAiB,EAAE,aAAa,IAAI;GAClD;IACA,CAAC,EAAO,KAAK,CAAC;AACjB,QACE,kBAAC,GAAD;EAAY,OAAO,EAAO;YACxB,kBAAC,GAAD;GAAgB,QAAQ;GAAY;GAAQ,CAAA;EACjC,CAAA"}
@@ -0,0 +1,41 @@
1
+ import { asArray as e, asFiniteNumber as t, asRecord as n, asRecordArray as r, displayValue as i } from "./shared.js";
2
+ import { toWidgetShim as a } from "../widgetShim.js";
3
+ import { EmbedFrame as o } from "./EmbedFrame.js";
4
+ import s from "../../widgets/retention/RetentionWidget.js";
5
+ import { useMemo as c } from "react";
6
+ import { jsx as l } from "react/jsx-runtime";
7
+ //#region src/bigconsole/components/embed/widgets/RetentionEmbed.tsx
8
+ var u = 100, d = 52;
9
+ function f(e) {
10
+ let n = [];
11
+ for (let r of e) {
12
+ let e = t(r);
13
+ if (e === null) break;
14
+ n.push(e);
15
+ }
16
+ return n;
17
+ }
18
+ function p({ widget: p }) {
19
+ let m = c(() => a(p), [p]), h = c(() => {
20
+ let a = n(p.data), o = e(a.periods).map(i).slice(0, d), s = r(a.cohorts, u).map((n) => ({
21
+ cohort: i(n.cohort),
22
+ initialSize: t(n.initialSize) ?? 0,
23
+ values: f(e(n.values).slice(0, d))
24
+ }));
25
+ return o.length > 0 && s.length > 0 ? {
26
+ periods: o,
27
+ cohorts: s
28
+ } : void 0;
29
+ }, [p.data]);
30
+ return /* @__PURE__ */ l(o, {
31
+ title: p.title,
32
+ children: /* @__PURE__ */ l(s, {
33
+ widget: m,
34
+ data: h
35
+ })
36
+ });
37
+ }
38
+ //#endregion
39
+ export { p as RetentionEmbed };
40
+
41
+ //# sourceMappingURL=RetentionEmbed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RetentionEmbed.js","names":[],"sources":["../../../../../src/bigconsole/components/embed/widgets/RetentionEmbed.tsx"],"sourcesContent":["/**\n * Read-only retention/cohort embed renderer (BOFF-7926).\n *\n * Reuses the in-app RetentionWidget. The service projects\n * `{ periods: string[], cohorts: [{ cohort, initialSize, values: (number | null)[] }] }`.\n */\nimport { useMemo } from 'react';\nimport { RetentionWidget, type RetentionData } from '../../widgets/retention/RetentionWidget';\nimport type { EmbedWidgetRenderProps } from '../types';\nimport { toWidgetShim } from '../widgetShim';\nimport { EmbedFrame } from './EmbedFrame';\nimport { asArray, asFiniteNumber, asRecord, asRecordArray, displayValue } from './shared';\n\nconst MAX_COHORTS = 100;\nconst MAX_PERIODS = 52;\n\n/**\n * Values up to the first missing period. The service sends `null` for periods a\n * cohort has not reached yet (always trailing); the in-app grid renders a\n * shorter row as blank cells, whereas a null cell would crash `toFixed`.\n */\nfunction leadingNumbers(values: unknown[]): number[] {\n const out: number[] = [];\n for (const v of values) {\n const n = asFiniteNumber(v);\n if (n === null) break;\n out.push(n);\n }\n return out;\n}\n\nexport function RetentionEmbed({ widget }: EmbedWidgetRenderProps) {\n const shim = useMemo(() => toWidgetShim(widget), [widget]);\n const data = useMemo((): RetentionData | undefined => {\n const d = asRecord(widget.data);\n const periods = asArray(d.periods).map(displayValue).slice(0, MAX_PERIODS);\n const cohorts = asRecordArray(d.cohorts, MAX_COHORTS).map((c) => ({\n cohort: displayValue(c.cohort),\n initialSize: asFiniteNumber(c.initialSize) ?? 0,\n values: leadingNumbers(asArray(c.values).slice(0, MAX_PERIODS)),\n }));\n return periods.length > 0 && cohorts.length > 0 ? { periods, cohorts } : undefined;\n }, [widget.data]);\n return (\n <EmbedFrame title={widget.title}>\n <RetentionWidget widget={shim} data={data} />\n </EmbedFrame>\n );\n}\n"],"mappings":";;;;;;;AAaA,IAAM,IAAc,KACd,IAAc;AAOpB,SAAS,EAAe,GAA6B;CACnD,IAAM,IAAgB,EAAE;AACxB,MAAK,IAAM,KAAK,GAAQ;EACtB,IAAM,IAAI,EAAe,EAAE;AAC3B,MAAI,MAAM,KAAM;AAChB,IAAI,KAAK,EAAE;;AAEb,QAAO;;AAGT,SAAgB,EAAe,EAAE,aAAkC;CACjE,IAAM,IAAO,QAAc,EAAa,EAAO,EAAE,CAAC,EAAO,CAAC,EACpD,IAAO,QAAyC;EACpD,IAAM,IAAI,EAAS,EAAO,KAAK,EACzB,IAAU,EAAQ,EAAE,QAAQ,CAAC,IAAI,EAAa,CAAC,MAAM,GAAG,EAAY,EACpE,IAAU,EAAc,EAAE,SAAS,EAAY,CAAC,KAAK,OAAO;GAChE,QAAQ,EAAa,EAAE,OAAO;GAC9B,aAAa,EAAe,EAAE,YAAY,IAAI;GAC9C,QAAQ,EAAe,EAAQ,EAAE,OAAO,CAAC,MAAM,GAAG,EAAY,CAAC;GAChE,EAAE;AACH,SAAO,EAAQ,SAAS,KAAK,EAAQ,SAAS,IAAI;GAAE;GAAS;GAAS,GAAG,KAAA;IACxE,CAAC,EAAO,KAAK,CAAC;AACjB,QACE,kBAAC,GAAD;EAAY,OAAO,EAAO;YACxB,kBAAC,GAAD;GAAiB,QAAQ;GAAY;GAAQ,CAAA;EAClC,CAAA"}
@@ -0,0 +1,40 @@
1
+ import { asFiniteNumber as e, asRecord as t, asRecordArray as n, displayValue as r, safeColor as i } from "./shared.js";
2
+ import { toWidgetShim as a } from "../widgetShim.js";
3
+ import { EmbedFrame as o } from "./EmbedFrame.js";
4
+ import s from "../../widgets/timeline/TimelineWidget.js";
5
+ import { useMemo as c } from "react";
6
+ import { jsx as l } from "react/jsx-runtime";
7
+ //#region src/bigconsole/components/embed/widgets/TimelineEmbed.tsx
8
+ var u = 1e3;
9
+ function d({ widget: d }) {
10
+ let f = c(() => a(d), [d]), p = c(() => {
11
+ let a = t(d.data), o = n(a.tasks, u).map((t, n) => ({
12
+ id: `t${n}`,
13
+ name: r(t.name),
14
+ start: r(t.start),
15
+ end: r(t.end),
16
+ progress: e(t.progress) ?? void 0,
17
+ color: i(t.color)
18
+ })).filter((e) => e.start !== "" && e.end !== ""), s = n(a.milestones, u).map((e, t) => ({
19
+ id: `m${t}`,
20
+ name: r(e.name),
21
+ date: r(e.date),
22
+ color: i(e.color)
23
+ })).filter((e) => e.date !== "");
24
+ return o.length > 0 || s.length > 0 ? {
25
+ tasks: o,
26
+ milestones: s
27
+ } : void 0;
28
+ }, [d.data]);
29
+ return /* @__PURE__ */ l(o, {
30
+ title: d.title,
31
+ children: /* @__PURE__ */ l(s, {
32
+ widget: f,
33
+ data: p
34
+ })
35
+ });
36
+ }
37
+ //#endregion
38
+ export { d as TimelineEmbed };
39
+
40
+ //# sourceMappingURL=TimelineEmbed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TimelineEmbed.js","names":[],"sources":["../../../../../src/bigconsole/components/embed/widgets/TimelineEmbed.tsx"],"sourcesContent":["/**\n * Read-only timeline embed renderer (BOFF-7926).\n *\n * Reuses the in-app TimelineWidget. The service projects\n * `{ tasks: [{id, name, start, end, progress?, color?}], milestones: [{name, date, color?}] }`\n * (ids are positional indexes; dependencies are dropped). Colors are re-checked.\n */\nimport { useMemo } from 'react';\nimport { TimelineWidget, type TimelineData } from '../../widgets/timeline/TimelineWidget';\nimport type { EmbedWidgetRenderProps } from '../types';\nimport { toWidgetShim } from '../widgetShim';\nimport { EmbedFrame } from './EmbedFrame';\nimport { asFiniteNumber, asRecord, asRecordArray, displayValue, safeColor } from './shared';\n\nconst MAX_ITEMS = 1000;\n\nexport function TimelineEmbed({ widget }: EmbedWidgetRenderProps) {\n const shim = useMemo(() => toWidgetShim(widget), [widget]);\n const data = useMemo((): TimelineData | undefined => {\n const d = asRecord(widget.data);\n const tasks = asRecordArray(d.tasks, MAX_ITEMS)\n .map((t, i) => ({\n id: `t${i}`,\n name: displayValue(t.name),\n start: displayValue(t.start),\n end: displayValue(t.end),\n progress: asFiniteNumber(t.progress) ?? undefined,\n color: safeColor(t.color),\n }))\n .filter((t) => t.start !== '' && t.end !== '');\n const milestones = asRecordArray(d.milestones, MAX_ITEMS)\n .map((m, i) => ({\n id: `m${i}`,\n name: displayValue(m.name),\n date: displayValue(m.date),\n color: safeColor(m.color),\n }))\n .filter((m) => m.date !== '');\n return tasks.length > 0 || milestones.length > 0 ? { tasks, milestones } : undefined;\n }, [widget.data]);\n return (\n <EmbedFrame title={widget.title}>\n <TimelineWidget widget={shim} data={data} />\n </EmbedFrame>\n );\n}\n"],"mappings":";;;;;;;AAcA,IAAM,IAAY;AAElB,SAAgB,EAAc,EAAE,aAAkC;CAChE,IAAM,IAAO,QAAc,EAAa,EAAO,EAAE,CAAC,EAAO,CAAC,EACpD,IAAO,QAAwC;EACnD,IAAM,IAAI,EAAS,EAAO,KAAK,EACzB,IAAQ,EAAc,EAAE,OAAO,EAAU,CAC5C,KAAK,GAAG,OAAO;GACd,IAAI,IAAI;GACR,MAAM,EAAa,EAAE,KAAK;GAC1B,OAAO,EAAa,EAAE,MAAM;GAC5B,KAAK,EAAa,EAAE,IAAI;GACxB,UAAU,EAAe,EAAE,SAAS,IAAI,KAAA;GACxC,OAAO,EAAU,EAAE,MAAM;GAC1B,EAAE,CACF,QAAQ,MAAM,EAAE,UAAU,MAAM,EAAE,QAAQ,GAAG,EAC1C,IAAa,EAAc,EAAE,YAAY,EAAU,CACtD,KAAK,GAAG,OAAO;GACd,IAAI,IAAI;GACR,MAAM,EAAa,EAAE,KAAK;GAC1B,MAAM,EAAa,EAAE,KAAK;GAC1B,OAAO,EAAU,EAAE,MAAM;GAC1B,EAAE,CACF,QAAQ,MAAM,EAAE,SAAS,GAAG;AAC/B,SAAO,EAAM,SAAS,KAAK,EAAW,SAAS,IAAI;GAAE;GAAO;GAAY,GAAG,KAAA;IAC1E,CAAC,EAAO,KAAK,CAAC;AACjB,QACE,kBAAC,GAAD;EAAY,OAAO,EAAO;YACxB,kBAAC,GAAD;GAAgB,QAAQ;GAAY;GAAQ,CAAA;EACjC,CAAA"}