@fayz-ai/plugin-reports 0.16.28 → 0.16.30
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.
- package/dist/AnalyticsModule-YIH6MDCC.js +4 -0
- package/dist/AnalyticsModule-YIH6MDCC.js.map +1 -0
- package/dist/AnalyticsOverview-R3JEMUQL.js +4 -0
- package/dist/AnalyticsOverview-R3JEMUQL.js.map +1 -0
- package/dist/AnalyticsReportViewer-U7SDU2LL.js +5 -0
- package/dist/AnalyticsReportViewer-U7SDU2LL.js.map +1 -0
- package/dist/AnalyticsSettingsPage-XWPM3VSA.js +4 -0
- package/dist/AnalyticsSettingsPage-XWPM3VSA.js.map +1 -0
- package/dist/LiveViewPage-XMVZHJ3U.js +16 -0
- package/dist/LiveViewPage-XMVZHJ3U.js.map +1 -0
- package/dist/ReportsListPage-7FAKQZ4T.js +4 -0
- package/dist/ReportsListPage-7FAKQZ4T.js.map +1 -0
- package/dist/chunk-67JOMNSD.js +127 -0
- package/dist/chunk-67JOMNSD.js.map +1 -0
- package/dist/chunk-DP7S37Y2.js +145 -0
- package/dist/chunk-DP7S37Y2.js.map +1 -0
- package/dist/chunk-DTUN4WLI.js +577 -0
- package/dist/chunk-DTUN4WLI.js.map +1 -0
- package/dist/chunk-LB6NOSJU.js +262 -0
- package/dist/chunk-LB6NOSJU.js.map +1 -0
- package/dist/chunk-S4MICM4C.js +23 -0
- package/dist/chunk-S4MICM4C.js.map +1 -0
- package/dist/chunk-YVLQV4N7.js +307 -0
- package/dist/chunk-YVLQV4N7.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +31 -2092
- package/dist/index.js.map +1 -1
- package/dist/migrations/main.browser.js +7 -0
- package/dist/migrations/main.browser.js.map +1 -0
- package/dist/migrations/main.js +692 -0
- package/dist/migrations/main.js.map +1 -0
- package/package.json +10 -4
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
import { useAnalyticsConfig, useAnalyticsCatalogue, useAnalyticsCards, useAnalyticsPrefs, analyticsSources, saveCustomReport, setCardPref, navigateAnalytics, REPORTS_BASE, ANALYTICS_BASE, resetAnalyticsPrefs, deleteCustomReport, setOverviewOrder, clearCardPref } from './chunk-YVLQV4N7.js';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { RotateCcw, ChevronUp, ChevronDown, EyeOff, Eye, Trash2, GripVertical, SlidersHorizontal } from 'lucide-react';
|
|
4
|
+
import { useTranslation } from '@fayz-ai/core';
|
|
5
|
+
import { SubpageHeader, PageHeaderActions, Button, Card, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Badge, getLucideIcon, Input, Checkbox } from '@fayz-ai/ui';
|
|
6
|
+
import { askAI, useAgentSurface, QuickActionsButton, RightRailPage } from '@fayz-ai/admin';
|
|
7
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
8
|
+
|
|
9
|
+
var VIZ_TYPES = ["kpi", "line", "bar", "area", "donut", "breakdown", "table"];
|
|
10
|
+
function vizOptionsFor(card) {
|
|
11
|
+
const hasDimension = (card.query.dimensions ?? []).some((d) => !d.hidden);
|
|
12
|
+
return hasDimension ? VIZ_TYPES : ["kpi", "table"];
|
|
13
|
+
}
|
|
14
|
+
function CardIcon({ name, className }) {
|
|
15
|
+
const Icon = getLucideIcon(name ?? "FileText") ?? getLucideIcon("FileText");
|
|
16
|
+
return Icon ? /* @__PURE__ */ jsx(Icon, { className }) : null;
|
|
17
|
+
}
|
|
18
|
+
function ReportRow({ card, custom, onEdit, children, drag }) {
|
|
19
|
+
const t = useTranslation();
|
|
20
|
+
return /* @__PURE__ */ jsxs(
|
|
21
|
+
"li",
|
|
22
|
+
{
|
|
23
|
+
draggable: Boolean(drag),
|
|
24
|
+
onDragStart: drag?.onDragStart,
|
|
25
|
+
onDragOver: drag?.onDragOver,
|
|
26
|
+
onDragEnd: drag?.onDragEnd,
|
|
27
|
+
className: [
|
|
28
|
+
"flex items-center gap-2 border-b border-border/60 px-3 py-2 last:border-0",
|
|
29
|
+
drag ? "cursor-grab active:cursor-grabbing" : "",
|
|
30
|
+
drag?.dragging ? "opacity-40" : ""
|
|
31
|
+
].join(" "),
|
|
32
|
+
children: [
|
|
33
|
+
drag ? /* @__PURE__ */ jsx(GripVertical, { className: "h-4 w-4 shrink-0 text-muted-foreground/50", "aria-hidden": true }) : /* @__PURE__ */ jsx("span", { className: "w-4 shrink-0", "aria-hidden": true }),
|
|
34
|
+
/* @__PURE__ */ jsx(CardIcon, { name: card.icon, className: "h-4 w-4 shrink-0 text-muted-foreground" }),
|
|
35
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
36
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
37
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-sm font-medium", children: card.name }),
|
|
38
|
+
custom ? /* @__PURE__ */ jsx(Badge, { variant: "secondary", children: t("reports.settings.custom") }) : null
|
|
39
|
+
] }),
|
|
40
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-xs text-muted-foreground", children: card.category })
|
|
41
|
+
] }),
|
|
42
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [
|
|
43
|
+
children,
|
|
44
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: onEdit, "aria-label": t("reports.settings.edit"), title: t("reports.settings.edit"), children: /* @__PURE__ */ jsx(SlidersHorizontal, { className: "h-3.5 w-3.5" }) })
|
|
45
|
+
] })
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
function CardEditor({ card, onClose }) {
|
|
51
|
+
const t = useTranslation();
|
|
52
|
+
const prefs = useAnalyticsPrefs();
|
|
53
|
+
const pref = prefs.cards[card.id] ?? {};
|
|
54
|
+
const isCustom = prefs.custom.some((r) => r.id === card.id);
|
|
55
|
+
const [name, setName] = React.useState(card.name);
|
|
56
|
+
const measures = card.query.measures ?? [];
|
|
57
|
+
const dimensions = (card.query.dimensions ?? []).filter((d) => !d.hidden);
|
|
58
|
+
const keptMeasures = new Set(pref.measures ?? measures.map((m) => m.key));
|
|
59
|
+
const keptDimensions = new Set(pref.dimensions ?? dimensions.map((d) => d.key));
|
|
60
|
+
const toggle = (set, key, field, all) => {
|
|
61
|
+
const next = new Set(set);
|
|
62
|
+
if (next.has(key)) next.delete(key);
|
|
63
|
+
else next.add(key);
|
|
64
|
+
setCardPref(card.id, { [field]: next.size === all.length ? void 0 : [...next] });
|
|
65
|
+
};
|
|
66
|
+
return (
|
|
67
|
+
// A coluna da direita, não uma gaveta só desta tela: ajustar um relatório é
|
|
68
|
+
// olhar a lista enquanto se mexe nele, e uma gaveta cobre exatamente a
|
|
69
|
+
// lista. É a mesma coluna do assistente — RightRailPage cai numa Sheet
|
|
70
|
+
// sozinha quando a shell não tem coluna nenhuma.
|
|
71
|
+
/* @__PURE__ */ jsxs(RightRailPage, { title: card.name, open: true, onClose, width: "max-w-md", children: [
|
|
72
|
+
/* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto p-4", children: /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
|
|
73
|
+
card.description ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: card.description }) : null,
|
|
74
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
75
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("reports.settings.name") }),
|
|
76
|
+
/* @__PURE__ */ jsx(
|
|
77
|
+
Input,
|
|
78
|
+
{
|
|
79
|
+
value: name,
|
|
80
|
+
onChange: (e) => setName(e.target.value),
|
|
81
|
+
onBlur: () => setCardPref(card.id, { name: name.trim() && name !== card.name ? name.trim() : void 0 })
|
|
82
|
+
}
|
|
83
|
+
)
|
|
84
|
+
] }),
|
|
85
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
86
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("reports.settings.viz") }),
|
|
87
|
+
/* @__PURE__ */ jsxs(
|
|
88
|
+
Select,
|
|
89
|
+
{
|
|
90
|
+
value: card.viz.type,
|
|
91
|
+
onValueChange: (value) => setCardPref(card.id, { viz: value }),
|
|
92
|
+
children: [
|
|
93
|
+
/* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
94
|
+
/* @__PURE__ */ jsx(SelectContent, { children: vizOptionsFor(card).map((viz) => /* @__PURE__ */ jsx(SelectItem, { value: viz, children: t(`reports.viz.${viz}`) }, viz)) })
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
)
|
|
98
|
+
] }),
|
|
99
|
+
dimensions.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
100
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground", children: t("reports.settings.dimensions") }),
|
|
101
|
+
dimensions.map((d) => /* @__PURE__ */ jsxs("label", { className: "flex cursor-pointer items-center gap-2 text-sm", children: [
|
|
102
|
+
/* @__PURE__ */ jsx(
|
|
103
|
+
Checkbox,
|
|
104
|
+
{
|
|
105
|
+
checked: keptDimensions.has(d.key),
|
|
106
|
+
onChange: () => toggle(keptDimensions, d.key, "dimensions", dimensions.map((x) => x.key))
|
|
107
|
+
}
|
|
108
|
+
),
|
|
109
|
+
d.label
|
|
110
|
+
] }, d.key))
|
|
111
|
+
] }) : null,
|
|
112
|
+
measures.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
113
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground", children: t("reports.settings.measures") }),
|
|
114
|
+
measures.map((m) => /* @__PURE__ */ jsxs("label", { className: "flex cursor-pointer items-center gap-2 text-sm", children: [
|
|
115
|
+
/* @__PURE__ */ jsx(
|
|
116
|
+
Checkbox,
|
|
117
|
+
{
|
|
118
|
+
checked: keptMeasures.has(m.key),
|
|
119
|
+
onChange: () => toggle(keptMeasures, m.key, "measures", measures.map((x) => x.key))
|
|
120
|
+
}
|
|
121
|
+
),
|
|
122
|
+
m.label
|
|
123
|
+
] }, m.key))
|
|
124
|
+
] }) : null
|
|
125
|
+
] }) }),
|
|
126
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-t border-border p-3", children: [
|
|
127
|
+
isCustom ? /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: () => {
|
|
128
|
+
deleteCustomReport(card.id);
|
|
129
|
+
onClose();
|
|
130
|
+
}, children: [
|
|
131
|
+
/* @__PURE__ */ jsx(Trash2, { className: "mr-1.5 h-3.5 w-3.5" }),
|
|
132
|
+
t("reports.settings.delete")
|
|
133
|
+
] }) : /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: () => {
|
|
134
|
+
clearCardPref(card.id);
|
|
135
|
+
onClose();
|
|
136
|
+
}, children: [
|
|
137
|
+
/* @__PURE__ */ jsx(RotateCcw, { className: "mr-1.5 h-3.5 w-3.5" }),
|
|
138
|
+
t("reports.settings.resetCard")
|
|
139
|
+
] }),
|
|
140
|
+
/* @__PURE__ */ jsx(Button, { size: "sm", onClick: onClose, children: t("reports.settings.done") })
|
|
141
|
+
] })
|
|
142
|
+
] })
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
function slugify(value) {
|
|
146
|
+
return value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "report";
|
|
147
|
+
}
|
|
148
|
+
function buildCustomReport(input) {
|
|
149
|
+
const dimensionKeys = new Set(input.source.dimensions.map((d) => d.key));
|
|
150
|
+
const measureKeys = new Set(input.source.measures.map((m) => m.key));
|
|
151
|
+
return {
|
|
152
|
+
id: `custom.${slugify(input.name)}-${Date.now().toString(36)}`,
|
|
153
|
+
name: input.name.trim(),
|
|
154
|
+
category: input.category.trim() || "Geral",
|
|
155
|
+
icon: "FileText",
|
|
156
|
+
createdBy: input.createdBy,
|
|
157
|
+
baseId: input.source.base.id,
|
|
158
|
+
// Filtered, not trusted: a field the source does not have would resolve to
|
|
159
|
+
// nothing and the report would open empty with no explanation.
|
|
160
|
+
dimensions: input.dimensions.filter((k) => dimensionKeys.has(k)),
|
|
161
|
+
measures: input.measures.filter((k) => measureKeys.has(k)),
|
|
162
|
+
viz: input.viz,
|
|
163
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function NewReportPanel({
|
|
167
|
+
sources,
|
|
168
|
+
categories,
|
|
169
|
+
onClose
|
|
170
|
+
}) {
|
|
171
|
+
const t = useTranslation();
|
|
172
|
+
const { createdBy } = useAnalyticsConfig();
|
|
173
|
+
const [name, setName] = React.useState("");
|
|
174
|
+
const [category, setCategory] = React.useState(categories[0] ?? "Geral");
|
|
175
|
+
const [sourceKeyState, setSourceKey] = React.useState(sources[0]?.key ?? "");
|
|
176
|
+
const [dimensions, setDimensions] = React.useState([]);
|
|
177
|
+
const [measures, setMeasures] = React.useState([]);
|
|
178
|
+
const [viz, setViz] = React.useState("table");
|
|
179
|
+
const source = sources.find((s) => s.key === sourceKeyState);
|
|
180
|
+
React.useEffect(() => {
|
|
181
|
+
setDimensions([]);
|
|
182
|
+
setMeasures([]);
|
|
183
|
+
}, [sourceKeyState]);
|
|
184
|
+
const toggle = (list, set, key) => {
|
|
185
|
+
set(list.includes(key) ? list.filter((k) => k !== key) : [...list, key]);
|
|
186
|
+
};
|
|
187
|
+
const error = !name.trim() ? t("reports.settings.needName") : measures.length === 0 ? t("reports.settings.needMeasure") : null;
|
|
188
|
+
const submit = () => {
|
|
189
|
+
if (error || !source) return;
|
|
190
|
+
saveCustomReport(buildCustomReport({
|
|
191
|
+
name,
|
|
192
|
+
category,
|
|
193
|
+
createdBy,
|
|
194
|
+
source,
|
|
195
|
+
dimensions,
|
|
196
|
+
measures,
|
|
197
|
+
viz
|
|
198
|
+
}));
|
|
199
|
+
onClose();
|
|
200
|
+
};
|
|
201
|
+
return /* @__PURE__ */ jsxs(RightRailPage, { title: t("reports.settings.new"), open: true, onClose, width: "max-w-md", children: [
|
|
202
|
+
/* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto p-4", children: /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
|
|
203
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
204
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("reports.settings.name") }),
|
|
205
|
+
/* @__PURE__ */ jsx(Input, { value: name, onChange: (e) => setName(e.target.value) })
|
|
206
|
+
] }),
|
|
207
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
208
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("reports.settings.category") }),
|
|
209
|
+
/* @__PURE__ */ jsx(Input, { value: category, onChange: (e) => setCategory(e.target.value), list: "fayz-analytics-categories" }),
|
|
210
|
+
/* @__PURE__ */ jsx("datalist", { id: "fayz-analytics-categories", children: categories.map((c) => /* @__PURE__ */ jsx("option", { value: c }, c)) })
|
|
211
|
+
] }),
|
|
212
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
213
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("reports.settings.source") }),
|
|
214
|
+
/* @__PURE__ */ jsxs(Select, { value: sourceKeyState, onValueChange: setSourceKey, children: [
|
|
215
|
+
/* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
216
|
+
/* @__PURE__ */ jsx(SelectContent, { children: sources.map((s) => /* @__PURE__ */ jsx(SelectItem, { value: s.key, children: s.label }, s.key)) })
|
|
217
|
+
] }),
|
|
218
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("reports.settings.sourceHint") })
|
|
219
|
+
] }),
|
|
220
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
221
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground", children: t("reports.settings.dimensions") }),
|
|
222
|
+
(source?.dimensions ?? []).length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "\u2014" }) : source.dimensions.map((d) => /* @__PURE__ */ jsxs("label", { className: "flex cursor-pointer items-center gap-2 text-sm", children: [
|
|
223
|
+
/* @__PURE__ */ jsx(Checkbox, { checked: dimensions.includes(d.key), onChange: () => toggle(dimensions, setDimensions, d.key) }),
|
|
224
|
+
d.label
|
|
225
|
+
] }, d.key))
|
|
226
|
+
] }),
|
|
227
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
228
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground", children: t("reports.settings.measures") }),
|
|
229
|
+
(source?.measures ?? []).map((m) => /* @__PURE__ */ jsxs("label", { className: "flex cursor-pointer items-center gap-2 text-sm", children: [
|
|
230
|
+
/* @__PURE__ */ jsx(Checkbox, { checked: measures.includes(m.key), onChange: () => toggle(measures, setMeasures, m.key) }),
|
|
231
|
+
m.label
|
|
232
|
+
] }, m.key))
|
|
233
|
+
] }),
|
|
234
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
235
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("reports.settings.viz") }),
|
|
236
|
+
/* @__PURE__ */ jsxs(Select, { value: viz, onValueChange: (value) => setViz(value), children: [
|
|
237
|
+
/* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
238
|
+
/* @__PURE__ */ jsx(SelectContent, { children: (dimensions.length > 0 ? VIZ_TYPES : ["kpi", "table"]).map((v) => /* @__PURE__ */ jsx(SelectItem, { value: v, children: t(`reports.viz.${v}`) }, v)) })
|
|
239
|
+
] })
|
|
240
|
+
] })
|
|
241
|
+
] }) }),
|
|
242
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 border-t border-border p-3", children: [
|
|
243
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-xs text-destructive", children: error }),
|
|
244
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [
|
|
245
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: onClose, children: t("reports.settings.cancel") }),
|
|
246
|
+
/* @__PURE__ */ jsx(Button, { size: "sm", onClick: submit, disabled: Boolean(error), children: t("reports.settings.save") })
|
|
247
|
+
] })
|
|
248
|
+
] })
|
|
249
|
+
] });
|
|
250
|
+
}
|
|
251
|
+
var CREATE_REPORT_TOOL = {
|
|
252
|
+
id: "reports.createAnalyticsReport",
|
|
253
|
+
name: "createAnalyticsReport",
|
|
254
|
+
description: "Creates a custom analytics report for this user. Pick ONE source from the `sources` list in the surface state, then group_by and measures USING ONLY that source's field keys \u2014 a key from another source resolves to nothing and the report opens empty. Understand the intent and propose the whole report in one call instead of asking field by field. Rules of thumb: one time dimension + one measure reads best as a line; a category dimension with one measure as bar or breakdown; three or more measures as a table; no dimension at all as a kpi.",
|
|
255
|
+
icon: "FileText",
|
|
256
|
+
mode: "persist",
|
|
257
|
+
execution: { plane: "client" },
|
|
258
|
+
category: "Analytics",
|
|
259
|
+
suggestions: [
|
|
260
|
+
{ label: "Um relat\xF3rio de receita por mesa" },
|
|
261
|
+
{ label: "Quanto cada pra\xE7a produz por turno" }
|
|
262
|
+
],
|
|
263
|
+
parameters: {
|
|
264
|
+
type: "object",
|
|
265
|
+
properties: {
|
|
266
|
+
name: { type: "string", description: "Report name, in the user's language." },
|
|
267
|
+
source: { type: "string", description: "A `key` from the surface state's `sources`." },
|
|
268
|
+
group_by: {
|
|
269
|
+
type: "array",
|
|
270
|
+
description: "Dimension keys from that source. Empty means a single headline number.",
|
|
271
|
+
items: { type: "string" }
|
|
272
|
+
},
|
|
273
|
+
measures: {
|
|
274
|
+
type: "array",
|
|
275
|
+
description: "Measure keys from that source. At least one.",
|
|
276
|
+
items: { type: "string" }
|
|
277
|
+
},
|
|
278
|
+
viz: {
|
|
279
|
+
type: "string",
|
|
280
|
+
enum: ["kpi", "line", "bar", "area", "donut", "breakdown", "table"],
|
|
281
|
+
description: "How to draw it. Defaults to table."
|
|
282
|
+
},
|
|
283
|
+
category: { type: "string", description: "Group it sits under in the rail. Reuse an existing one when it fits." },
|
|
284
|
+
pin: { type: "boolean", description: "Also place it on the overview grid." }
|
|
285
|
+
},
|
|
286
|
+
required: ["name", "source", "measures"]
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
var AGENT_INSTRUCTIONS = "The user is on the report configuration screen. `sources` in the surface state lists every data source with the exact dimension and measure keys askable of it \u2014 use those keys verbatim. When they describe a report, call createAnalyticsReport once with the full shape; do not interview them field by field. Name the report the way they said it. After creating, say in one line what it groups by and what it measures.";
|
|
290
|
+
function AnalyticsSettingsPage() {
|
|
291
|
+
const t = useTranslation();
|
|
292
|
+
const { labels } = useAnalyticsConfig();
|
|
293
|
+
const catalogue = useAnalyticsCatalogue();
|
|
294
|
+
const cards = useAnalyticsCards();
|
|
295
|
+
const prefs = useAnalyticsPrefs();
|
|
296
|
+
const { createdBy } = useAnalyticsConfig();
|
|
297
|
+
const [editing, setEditing] = React.useState(null);
|
|
298
|
+
const [creating, setCreating] = React.useState(false);
|
|
299
|
+
const [dragId, setDragId] = React.useState(null);
|
|
300
|
+
const askIntro = React.useMemo(() => ({
|
|
301
|
+
role: "setup",
|
|
302
|
+
persona: t("reports.settings.askPersona"),
|
|
303
|
+
pitch: t("reports.settings.askPitch"),
|
|
304
|
+
description: t("reports.settings.askDescription"),
|
|
305
|
+
suggestions: [0, 1, 2].map((i) => ({ label: t(`reports.settings.askSuggestion.${i}`) }))
|
|
306
|
+
}), [t]);
|
|
307
|
+
const createActions = React.useMemo(() => [
|
|
308
|
+
{
|
|
309
|
+
id: "with-ai",
|
|
310
|
+
label: t("assistant.askAI"),
|
|
311
|
+
description: t("reports.settings.newWithAIHint"),
|
|
312
|
+
icon: "WandSparkles",
|
|
313
|
+
highlight: true,
|
|
314
|
+
action: () => askAI({ hint: t("reports.settings.askPlaceholder"), intro: askIntro })
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
id: "by-hand",
|
|
318
|
+
label: t("reports.settings.newByHand"),
|
|
319
|
+
description: t("reports.settings.newByHandHint"),
|
|
320
|
+
icon: "SlidersHorizontal",
|
|
321
|
+
action: () => setCreating(true)
|
|
322
|
+
}
|
|
323
|
+
], [t, askIntro]);
|
|
324
|
+
const sources = React.useMemo(() => analyticsSources(cards), [cards]);
|
|
325
|
+
const categories = React.useMemo(
|
|
326
|
+
() => [...new Set(cards.map((c) => c.category))].sort((a, b) => a.localeCompare(b)),
|
|
327
|
+
[cards]
|
|
328
|
+
);
|
|
329
|
+
const pinned = React.useMemo(
|
|
330
|
+
() => cards.filter((c) => c.dashboard).sort((a, b) => (a.dashboard?.order ?? 0) - (b.dashboard?.order ?? 0)),
|
|
331
|
+
[cards]
|
|
332
|
+
);
|
|
333
|
+
const unpinned = React.useMemo(
|
|
334
|
+
() => cards.filter((c) => !c.dashboard).sort((a, b) => a.category.localeCompare(b.category) || a.name.localeCompare(b.name)),
|
|
335
|
+
[cards]
|
|
336
|
+
);
|
|
337
|
+
const removed = React.useMemo(
|
|
338
|
+
() => catalogue.filter((c) => prefs.cards[c.id]?.hidden),
|
|
339
|
+
[catalogue, prefs]
|
|
340
|
+
);
|
|
341
|
+
const [order, setOrder] = React.useState(null);
|
|
342
|
+
const pinnedIds = order ?? pinned.map((c) => c.id);
|
|
343
|
+
const pinnedInOrder = pinnedIds.map((id) => pinned.find((c) => c.id === id)).filter((c) => Boolean(c));
|
|
344
|
+
const move = (id, delta) => {
|
|
345
|
+
const ids = pinned.map((c) => c.id);
|
|
346
|
+
const from = ids.indexOf(id);
|
|
347
|
+
const to = from + delta;
|
|
348
|
+
if (from < 0 || to < 0 || to >= ids.length) return;
|
|
349
|
+
ids.splice(to, 0, ids.splice(from, 1)[0]);
|
|
350
|
+
setOverviewOrder(ids);
|
|
351
|
+
};
|
|
352
|
+
const onDragOver = (targetId) => (event) => {
|
|
353
|
+
event.preventDefault();
|
|
354
|
+
if (!dragId || dragId === targetId) return;
|
|
355
|
+
const ids = [...pinnedIds];
|
|
356
|
+
const from = ids.indexOf(dragId);
|
|
357
|
+
const to = ids.indexOf(targetId);
|
|
358
|
+
if (from < 0 || to < 0) return;
|
|
359
|
+
ids.splice(to, 0, ids.splice(from, 1)[0]);
|
|
360
|
+
setOrder(ids);
|
|
361
|
+
};
|
|
362
|
+
const commitDrag = () => {
|
|
363
|
+
if (order) setOverviewOrder(order);
|
|
364
|
+
setOrder(null);
|
|
365
|
+
setDragId(null);
|
|
366
|
+
};
|
|
367
|
+
const editingCard = cards.find((c) => c.id === editing);
|
|
368
|
+
const isCustom = (id) => prefs.custom.some((r) => r.id === id);
|
|
369
|
+
const surfaceRef = React.useRef({ sources, categories, cards, prefs, createdBy });
|
|
370
|
+
surfaceRef.current = { sources, categories, cards, prefs, createdBy };
|
|
371
|
+
const agentSurface = React.useMemo(() => ({
|
|
372
|
+
id: "analytics.reports",
|
|
373
|
+
pluginId: "reports",
|
|
374
|
+
instructions: AGENT_INSTRUCTIONS,
|
|
375
|
+
describe: () => {
|
|
376
|
+
const o = surfaceRef.current;
|
|
377
|
+
return {
|
|
378
|
+
title: "Configura\xE7\xE3o de relat\xF3rios",
|
|
379
|
+
state: {
|
|
380
|
+
sources: o.sources.map((source) => ({
|
|
381
|
+
key: source.key,
|
|
382
|
+
label: source.label,
|
|
383
|
+
grain: source.base.grain ?? null,
|
|
384
|
+
group_by: source.dimensions.map((d) => ({ key: d.key, label: d.label })),
|
|
385
|
+
measures: source.measures.map((m) => ({ key: m.key, label: m.label, format: m.format ?? "number" }))
|
|
386
|
+
})),
|
|
387
|
+
categories: o.categories,
|
|
388
|
+
onOverview: o.cards.filter((c) => c.dashboard).map((c) => c.name),
|
|
389
|
+
yourReports: o.prefs.custom.map((r) => r.name)
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
},
|
|
393
|
+
tools: [{
|
|
394
|
+
tool: CREATE_REPORT_TOOL,
|
|
395
|
+
execute: (args) => {
|
|
396
|
+
const o = surfaceRef.current;
|
|
397
|
+
const source = o.sources.find((s) => s.key === args.source) ?? o.sources.find((s) => s.label === args.source);
|
|
398
|
+
if (!source) {
|
|
399
|
+
return { ok: false, error: "unknown_source", sources: o.sources.map((s) => s.key) };
|
|
400
|
+
}
|
|
401
|
+
const report = buildCustomReport({
|
|
402
|
+
name: String(args.name ?? "").trim() || "Relat\xF3rio",
|
|
403
|
+
category: String(args.category ?? o.categories[0] ?? "Geral"),
|
|
404
|
+
createdBy: o.createdBy,
|
|
405
|
+
source,
|
|
406
|
+
dimensions: Array.isArray(args.group_by) ? args.group_by : [],
|
|
407
|
+
measures: Array.isArray(args.measures) ? args.measures : [],
|
|
408
|
+
viz: args.viz ?? "table"
|
|
409
|
+
});
|
|
410
|
+
if (report.measures.length === 0) {
|
|
411
|
+
return {
|
|
412
|
+
ok: false,
|
|
413
|
+
error: "no_valid_measure",
|
|
414
|
+
available: source.measures.map((m) => m.key)
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
saveCustomReport(report);
|
|
418
|
+
if (args.pin) setCardPref(report.id, { pinned: true, order: 999 });
|
|
419
|
+
navigateAnalytics(`${REPORTS_BASE}/${encodeURIComponent(report.id)}`);
|
|
420
|
+
return {
|
|
421
|
+
ok: true,
|
|
422
|
+
id: report.id,
|
|
423
|
+
name: report.name,
|
|
424
|
+
group_by: report.dimensions,
|
|
425
|
+
measures: report.measures,
|
|
426
|
+
viz: report.viz
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
}]
|
|
430
|
+
}), []);
|
|
431
|
+
useAgentSurface(agentSurface);
|
|
432
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
433
|
+
/* @__PURE__ */ jsx(
|
|
434
|
+
SubpageHeader,
|
|
435
|
+
{
|
|
436
|
+
title: t("reports.settings.title"),
|
|
437
|
+
parentLabel: labels.analytics,
|
|
438
|
+
onBack: () => navigateAnalytics(ANALYTICS_BASE)
|
|
439
|
+
}
|
|
440
|
+
),
|
|
441
|
+
/* @__PURE__ */ jsxs(PageHeaderActions, { children: [
|
|
442
|
+
/* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: () => resetAnalyticsPrefs(), children: [
|
|
443
|
+
/* @__PURE__ */ jsx(RotateCcw, { className: "mr-1.5 h-3.5 w-3.5" }),
|
|
444
|
+
t("reports.settings.reset")
|
|
445
|
+
] }),
|
|
446
|
+
/* @__PURE__ */ jsx(QuickActionsButton, { label: t("reports.settings.new"), variant: "default", actions: createActions })
|
|
447
|
+
] }),
|
|
448
|
+
/* @__PURE__ */ jsx("p", { className: "max-w-2xl text-sm text-muted-foreground", children: t("reports.settings.subtitle") }),
|
|
449
|
+
/* @__PURE__ */ jsxs(Card, { className: "overflow-hidden", children: [
|
|
450
|
+
/* @__PURE__ */ jsxs("div", { className: "border-b border-border px-3 py-2", children: [
|
|
451
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: t("reports.settings.onOverview") }),
|
|
452
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("reports.settings.onOverviewHint") })
|
|
453
|
+
] }),
|
|
454
|
+
pinnedInOrder.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-3 py-6 text-center text-sm text-muted-foreground", children: t("reports.settings.empty") }) : /* @__PURE__ */ jsx("ul", { children: pinnedInOrder.map((card, index) => /* @__PURE__ */ jsxs(
|
|
455
|
+
ReportRow,
|
|
456
|
+
{
|
|
457
|
+
card,
|
|
458
|
+
custom: isCustom(card.id),
|
|
459
|
+
onEdit: () => setEditing(card.id),
|
|
460
|
+
drag: {
|
|
461
|
+
dragging: dragId === card.id,
|
|
462
|
+
onDragStart: () => setDragId(card.id),
|
|
463
|
+
onDragOver: onDragOver(card.id),
|
|
464
|
+
onDragEnd: commitDrag
|
|
465
|
+
},
|
|
466
|
+
children: [
|
|
467
|
+
/* @__PURE__ */ jsxs(
|
|
468
|
+
Select,
|
|
469
|
+
{
|
|
470
|
+
value: String(card.dashboard?.span ?? ""),
|
|
471
|
+
onValueChange: (value) => setCardPref(card.id, { span: Number(value) }),
|
|
472
|
+
children: [
|
|
473
|
+
/* @__PURE__ */ jsx(SelectTrigger, { className: "h-8 w-[150px]", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: t("reports.settings.width") }) }),
|
|
474
|
+
/* @__PURE__ */ jsx(SelectContent, { children: [1, 2, 3, 4].map((span) => /* @__PURE__ */ jsx(SelectItem, { value: String(span), children: t(`reports.settings.width.${span}`) }, span)) })
|
|
475
|
+
]
|
|
476
|
+
}
|
|
477
|
+
),
|
|
478
|
+
/* @__PURE__ */ jsx(
|
|
479
|
+
Button,
|
|
480
|
+
{
|
|
481
|
+
variant: "ghost",
|
|
482
|
+
size: "sm",
|
|
483
|
+
disabled: index === 0,
|
|
484
|
+
onClick: () => move(card.id, -1),
|
|
485
|
+
"aria-label": t("reports.settings.up"),
|
|
486
|
+
title: t("reports.settings.up"),
|
|
487
|
+
children: /* @__PURE__ */ jsx(ChevronUp, { className: "h-3.5 w-3.5" })
|
|
488
|
+
}
|
|
489
|
+
),
|
|
490
|
+
/* @__PURE__ */ jsx(
|
|
491
|
+
Button,
|
|
492
|
+
{
|
|
493
|
+
variant: "ghost",
|
|
494
|
+
size: "sm",
|
|
495
|
+
disabled: index === pinnedInOrder.length - 1,
|
|
496
|
+
onClick: () => move(card.id, 1),
|
|
497
|
+
"aria-label": t("reports.settings.down"),
|
|
498
|
+
title: t("reports.settings.down"),
|
|
499
|
+
children: /* @__PURE__ */ jsx(ChevronDown, { className: "h-3.5 w-3.5" })
|
|
500
|
+
}
|
|
501
|
+
),
|
|
502
|
+
/* @__PURE__ */ jsx(
|
|
503
|
+
Button,
|
|
504
|
+
{
|
|
505
|
+
variant: "ghost",
|
|
506
|
+
size: "sm",
|
|
507
|
+
onClick: () => setCardPref(card.id, { pinned: false }),
|
|
508
|
+
"aria-label": t("reports.settings.unpin"),
|
|
509
|
+
title: t("reports.settings.unpin"),
|
|
510
|
+
children: /* @__PURE__ */ jsx(EyeOff, { className: "h-3.5 w-3.5" })
|
|
511
|
+
}
|
|
512
|
+
)
|
|
513
|
+
]
|
|
514
|
+
},
|
|
515
|
+
card.id
|
|
516
|
+
)) })
|
|
517
|
+
] }),
|
|
518
|
+
/* @__PURE__ */ jsxs(Card, { className: "overflow-hidden", children: [
|
|
519
|
+
/* @__PURE__ */ jsxs("div", { className: "border-b border-border px-3 py-2", children: [
|
|
520
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: t("reports.settings.offOverview") }),
|
|
521
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("reports.settings.offOverviewHint") })
|
|
522
|
+
] }),
|
|
523
|
+
/* @__PURE__ */ jsx("ul", { children: unpinned.map((card) => /* @__PURE__ */ jsxs(ReportRow, { card, custom: isCustom(card.id), onEdit: () => setEditing(card.id), children: [
|
|
524
|
+
/* @__PURE__ */ jsx(
|
|
525
|
+
Button,
|
|
526
|
+
{
|
|
527
|
+
variant: "ghost",
|
|
528
|
+
size: "sm",
|
|
529
|
+
onClick: () => setCardPref(card.id, { pinned: true, order: pinned.length }),
|
|
530
|
+
"aria-label": t("reports.settings.pin"),
|
|
531
|
+
title: t("reports.settings.pin"),
|
|
532
|
+
children: /* @__PURE__ */ jsx(Eye, { className: "h-3.5 w-3.5" })
|
|
533
|
+
}
|
|
534
|
+
),
|
|
535
|
+
isCustom(card.id) ? /* @__PURE__ */ jsx(
|
|
536
|
+
Button,
|
|
537
|
+
{
|
|
538
|
+
variant: "ghost",
|
|
539
|
+
size: "sm",
|
|
540
|
+
onClick: () => deleteCustomReport(card.id),
|
|
541
|
+
"aria-label": t("reports.settings.delete"),
|
|
542
|
+
title: t("reports.settings.delete"),
|
|
543
|
+
children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
|
|
544
|
+
}
|
|
545
|
+
) : /* @__PURE__ */ jsx(
|
|
546
|
+
Button,
|
|
547
|
+
{
|
|
548
|
+
variant: "ghost",
|
|
549
|
+
size: "sm",
|
|
550
|
+
onClick: () => setCardPref(card.id, { hidden: true }),
|
|
551
|
+
"aria-label": t("reports.settings.remove"),
|
|
552
|
+
title: t("reports.settings.remove"),
|
|
553
|
+
children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
|
|
554
|
+
}
|
|
555
|
+
)
|
|
556
|
+
] }, card.id)) })
|
|
557
|
+
] }),
|
|
558
|
+
removed.length > 0 ? /* @__PURE__ */ jsxs(Card, { className: "overflow-hidden", children: [
|
|
559
|
+
/* @__PURE__ */ jsx("div", { className: "border-b border-border px-3 py-2", children: /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: t("reports.settings.hidden") }) }),
|
|
560
|
+
/* @__PURE__ */ jsx("ul", { children: removed.map((card) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2 border-b border-border/60 px-3 py-2 last:border-0", children: [
|
|
561
|
+
/* @__PURE__ */ jsx("span", { className: "w-4 shrink-0", "aria-hidden": true }),
|
|
562
|
+
/* @__PURE__ */ jsx(CardIcon, { name: card.icon, className: "h-4 w-4 shrink-0 text-muted-foreground" }),
|
|
563
|
+
/* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx("span", { className: "truncate text-sm text-muted-foreground", children: card.name }) }),
|
|
564
|
+
/* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: () => setCardPref(card.id, { hidden: void 0 }), children: [
|
|
565
|
+
/* @__PURE__ */ jsx(RotateCcw, { className: "mr-1.5 h-3.5 w-3.5" }),
|
|
566
|
+
t("reports.settings.restore")
|
|
567
|
+
] })
|
|
568
|
+
] }, card.id)) })
|
|
569
|
+
] }) : null,
|
|
570
|
+
editingCard ? /* @__PURE__ */ jsx(CardEditor, { card: editingCard, onClose: () => setEditing(null) }) : null,
|
|
571
|
+
creating ? /* @__PURE__ */ jsx(NewReportPanel, { sources, categories, onClose: () => setCreating(false) }) : null
|
|
572
|
+
] });
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
export { AnalyticsSettingsPage, buildCustomReport };
|
|
576
|
+
//# sourceMappingURL=chunk-DTUN4WLI.js.map
|
|
577
|
+
//# sourceMappingURL=chunk-DTUN4WLI.js.map
|