@fayz-ai/plugin-reports 0.11.0 → 0.12.0
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/{chunk-4SPBNVQA.js → chunk-5SO5HWV2.js} +27 -4
- package/dist/chunk-5SO5HWV2.js.map +1 -0
- package/dist/components/ExportDropdown.d.ts.map +1 -1
- package/dist/components/ReportPagination.d.ts.map +1 -1
- package/dist/{excel-KIJTC7OJ.js → excel-LDM5H5IW.js} +3 -3
- package/dist/{excel-KIJTC7OJ.js.map → excel-LDM5H5IW.js.map} +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +962 -62
- package/dist/index.js.map +1 -1
- package/dist/lib/columns.d.ts.map +1 -1
- package/dist/lib/preferences.d.ts +90 -0
- package/dist/lib/preferences.d.ts.map +1 -0
- package/dist/locales/en.d.ts.map +1 -1
- package/dist/locales/pt-BR.d.ts.map +1 -1
- package/dist/{pdf-7QQ3U5JQ.js → pdf-XA2AR6B4.js} +3 -3
- package/dist/{pdf-7QQ3U5JQ.js.map → pdf-XA2AR6B4.js.map} +1 -1
- package/dist/types.d.ts +4 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/views/AnalyticsAssistant.d.ts.map +1 -1
- package/dist/views/AnalyticsModule.d.ts +1 -1
- package/dist/views/AnalyticsModule.d.ts.map +1 -1
- package/dist/views/AnalyticsOverview.d.ts.map +1 -1
- package/dist/views/AnalyticsReportViewer.d.ts.map +1 -1
- package/dist/views/AnalyticsSettingsPage.d.ts +16 -0
- package/dist/views/AnalyticsSettingsPage.d.ts.map +1 -0
- package/dist/views/AnalyticsSurface.d.ts +22 -3
- package/dist/views/AnalyticsSurface.d.ts.map +1 -1
- package/package.json +7 -7
- package/dist/chunk-4SPBNVQA.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { reportColumnsToTanstack, formatValuePlain } from './chunk-
|
|
2
|
-
import * as
|
|
3
|
-
import
|
|
4
|
-
import { usePluginRuntime, getAnalyticsCards, registerTranslations, createSafeDataProvider, getSupabaseClientOptional, hashRouterAdapter,
|
|
5
|
-
import { useOrganizationStore, useShellRailAvailable, useContextualNav, AssistantInvite } from '@fayz-ai/admin';
|
|
6
|
-
import { AnalyticsContextProvider, useModuleLayout, ModulePage, collectFilters, AnalyticsControls, AnalyticsGrid, Card, FilterChip, DataTable, Button, SubpageHeader, useAnalyticsCard, useAnalyticsContext, AnalyticsCard, Dropdown, DropdownTrigger, DropdownContent, DropdownItem } from '@fayz-ai/ui';
|
|
1
|
+
import { reportColumnsToTanstack, formatValuePlain } from './chunk-5SO5HWV2.js';
|
|
2
|
+
import * as React10 from 'react';
|
|
3
|
+
import React10__default, { useState, useEffect } from 'react';
|
|
4
|
+
import { usePluginRuntime, getAnalyticsCards, useTranslation, registerTranslations, createSafeDataProvider, getSupabaseClientOptional, hashRouterAdapter, exportCSV } from '@fayz-ai/core';
|
|
5
|
+
import { useOrganizationStore, useShellRailAvailable, useContextualNav, askAI, useAgentSurface, QuickActionsButton, AssistantInvite, RightRailPage } from '@fayz-ai/admin';
|
|
6
|
+
import { AnalyticsContextProvider, useModuleLayout, ModulePage, collectFilters, AnalyticsControls, AnalyticsGrid, Card, FilterChip, DataTable, Button, SubpageHeader, useAnalyticsCard, ToolbarAction, PageHeaderActions, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, useAnalyticsContext, AnalyticsCard, Dropdown, DropdownTrigger, DropdownContent, DropdownItem, Badge, getLucideIcon, Input, Checkbox } from '@fayz-ai/ui';
|
|
7
7
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
8
|
-
import { BarChart3, User, Search, ArrowLeft, Printer, Construction, Download, Radio, ChevronLeft, ChevronRight } from 'lucide-react';
|
|
8
|
+
import { BarChart3, User, Search, ArrowLeft, Printer, RotateCcw, ChevronUp, ChevronDown, EyeOff, Eye, Trash2, Construction, Download, GripVertical, SlidersHorizontal, Radio, ChevronLeft, ChevronRight } from 'lucide-react';
|
|
9
9
|
|
|
10
10
|
// src/data/aggregate.ts
|
|
11
11
|
function num(value) {
|
|
@@ -496,6 +496,68 @@ var en = {
|
|
|
496
496
|
"reports.assistant.suggestion.0": "How is my business doing this month?",
|
|
497
497
|
"reports.assistant.suggestion.1": "What's growing and what's falling?",
|
|
498
498
|
"reports.assistant.suggestion.2": "Where am I losing money?",
|
|
499
|
+
"reports.print": "Print",
|
|
500
|
+
"reports.pagination.range": "{{from}}\u2013{{to}} of {{total}}",
|
|
501
|
+
"reports.pagination.empty": "No results",
|
|
502
|
+
"reports.pagination.perPage": "{{size}} / page",
|
|
503
|
+
"reports.settings.nav": "Configure reports",
|
|
504
|
+
"reports.settings.title": "Configure reports",
|
|
505
|
+
"reports.settings.subtitle": "Choose what appears on the overview, in what order, and what each report shows. Saved in this browser.",
|
|
506
|
+
"reports.settings.new": "New report",
|
|
507
|
+
"reports.settings.newHint": "A new report asks a different question of a source the app already exposes.",
|
|
508
|
+
"reports.settings.newWithAIHint": "Describe what you want to see and I build it.",
|
|
509
|
+
"reports.settings.newByHand": "Create by hand",
|
|
510
|
+
"reports.settings.newByHandHint": "Pick the source, the grouping and the measures.",
|
|
511
|
+
"reports.settings.askPersona": "Report analyst",
|
|
512
|
+
"reports.settings.askPitch": "Tell me the report you want.",
|
|
513
|
+
"reports.settings.askDescription": "I know this app's data sources and the fields on each. Describe what you want to see \u2014 I pick the grouping, the measures and the format, build it and open it for you to check.",
|
|
514
|
+
"reports.settings.askSuggestion.0": "Revenue by table, highest first",
|
|
515
|
+
"reports.settings.askSuggestion.1": "How much each station produces per service",
|
|
516
|
+
"reports.settings.askSuggestion.2": "Average ticket by channel, month by month",
|
|
517
|
+
"reports.settings.askPlaceholder": "Describe the report\u2026",
|
|
518
|
+
"reports.settings.orByHand": "or build it by hand",
|
|
519
|
+
"reports.settings.reset": "Reset to defaults",
|
|
520
|
+
"reports.settings.onOverview": "On the overview",
|
|
521
|
+
"reports.settings.onOverviewHint": "Drag to reorder. Width is in grid columns.",
|
|
522
|
+
"reports.settings.offOverview": "Off the overview",
|
|
523
|
+
"reports.settings.offOverviewHint": "Still in the list and still open full size \u2014 they just do not claim a tile.",
|
|
524
|
+
"reports.settings.hidden": "Removed from the catalogue",
|
|
525
|
+
"reports.settings.empty": "No reports on the overview.",
|
|
526
|
+
"reports.settings.custom": "Yours",
|
|
527
|
+
"reports.settings.pin": "Add to the overview",
|
|
528
|
+
"reports.settings.unpin": "Remove from the overview",
|
|
529
|
+
"reports.settings.up": "Move up",
|
|
530
|
+
"reports.settings.down": "Move down",
|
|
531
|
+
"reports.settings.edit": "Adjust",
|
|
532
|
+
"reports.settings.remove": "Remove from the catalogue",
|
|
533
|
+
"reports.settings.restore": "Restore",
|
|
534
|
+
"reports.settings.delete": "Delete",
|
|
535
|
+
"reports.settings.width": "Width",
|
|
536
|
+
"reports.settings.width.1": "Quarter",
|
|
537
|
+
"reports.settings.width.2": "Half",
|
|
538
|
+
"reports.settings.width.3": "Three quarters",
|
|
539
|
+
"reports.settings.width.4": "Full",
|
|
540
|
+
"reports.settings.name": "Name",
|
|
541
|
+
"reports.settings.category": "Category",
|
|
542
|
+
"reports.settings.source": "Data source",
|
|
543
|
+
"reports.settings.sourceHint": "Where the rows come from \u2014 it decides which fields you can use.",
|
|
544
|
+
"reports.settings.viz": "Visualization",
|
|
545
|
+
"reports.settings.dimensions": "Group by",
|
|
546
|
+
"reports.settings.measures": "Measures",
|
|
547
|
+
"reports.settings.resetCard": "Reset this report",
|
|
548
|
+
"reports.settings.needName": "Give the report a name.",
|
|
549
|
+
"reports.settings.needMeasure": "Pick at least one measure.",
|
|
550
|
+
"reports.settings.save": "Create",
|
|
551
|
+
"reports.settings.cancel": "Cancel",
|
|
552
|
+
"reports.settings.done": "Done",
|
|
553
|
+
"reports.viz.kpi": "Number",
|
|
554
|
+
"reports.viz.line": "Line",
|
|
555
|
+
"reports.viz.bar": "Bars",
|
|
556
|
+
"reports.viz.area": "Area",
|
|
557
|
+
"reports.viz.donut": "Donut",
|
|
558
|
+
"reports.viz.breakdown": "Ranking",
|
|
559
|
+
"reports.viz.table": "Table",
|
|
560
|
+
"reports.viz.funnel": "Funnel",
|
|
499
561
|
"reports.unavailableTitle": "Coming soon",
|
|
500
562
|
"reports.unavailableDescription": "This report is not available yet. It will be enabled in a future update."
|
|
501
563
|
};
|
|
@@ -535,6 +597,68 @@ var ptBR = {
|
|
|
535
597
|
"reports.assistant.suggestion.0": "Como est\xE1 meu neg\xF3cio neste m\xEAs?",
|
|
536
598
|
"reports.assistant.suggestion.1": "O que est\xE1 crescendo e o que est\xE1 caindo?",
|
|
537
599
|
"reports.assistant.suggestion.2": "Onde estou perdendo dinheiro?",
|
|
600
|
+
"reports.print": "Imprimir",
|
|
601
|
+
"reports.pagination.range": "{{from}}\u2013{{to}} de {{total}}",
|
|
602
|
+
"reports.pagination.empty": "Nenhum resultado",
|
|
603
|
+
"reports.pagination.perPage": "{{size}} / p\xE1gina",
|
|
604
|
+
"reports.settings.nav": "Configurar relat\xF3rios",
|
|
605
|
+
"reports.settings.title": "Configurar relat\xF3rios",
|
|
606
|
+
"reports.settings.subtitle": "Escolha o que aparece no painel, em que ordem e o que cada relat\xF3rio mostra. As escolhas ficam neste navegador.",
|
|
607
|
+
"reports.settings.new": "Novo relat\xF3rio",
|
|
608
|
+
"reports.settings.newHint": "Um relat\xF3rio novo pergunta outra coisa a uma fonte que o app j\xE1 exp\xF5e.",
|
|
609
|
+
"reports.settings.newWithAIHint": "Descreva o que quer ver e eu monto.",
|
|
610
|
+
"reports.settings.newByHand": "Criar manualmente",
|
|
611
|
+
"reports.settings.newByHandHint": "Escolher fonte, agrupamento e medidas.",
|
|
612
|
+
"reports.settings.askPersona": "Analista de relat\xF3rios",
|
|
613
|
+
"reports.settings.askPitch": "Diga o relat\xF3rio que voc\xEA quer.",
|
|
614
|
+
"reports.settings.askDescription": "Conhe\xE7o as fontes deste app e os campos de cada uma. Descreva o que quer ver \u2014 eu escolho o agrupamento, as medidas e o formato, monto e abro para voc\xEA conferir.",
|
|
615
|
+
"reports.settings.askSuggestion.0": "Receita por mesa, do maior para o menor",
|
|
616
|
+
"reports.settings.askSuggestion.1": "Quanto cada pra\xE7a produz por turno",
|
|
617
|
+
"reports.settings.askSuggestion.2": "Ticket m\xE9dio por canal, m\xEAs a m\xEAs",
|
|
618
|
+
"reports.settings.askPlaceholder": "Descreva o relat\xF3rio\u2026",
|
|
619
|
+
"reports.settings.orByHand": "ou monte \xE0 m\xE3o",
|
|
620
|
+
"reports.settings.reset": "Restaurar padr\xF5es",
|
|
621
|
+
"reports.settings.onOverview": "No painel",
|
|
622
|
+
"reports.settings.onOverviewHint": "Arraste para reordenar. A largura \xE9 em colunas da grade.",
|
|
623
|
+
"reports.settings.offOverview": "Fora do painel",
|
|
624
|
+
"reports.settings.offOverviewHint": "Continuam na lista e abrem inteiros; s\xF3 n\xE3o ocupam um bloco do painel.",
|
|
625
|
+
"reports.settings.hidden": "Removidos do cat\xE1logo",
|
|
626
|
+
"reports.settings.empty": "Nenhum relat\xF3rio no painel.",
|
|
627
|
+
"reports.settings.custom": "Seu",
|
|
628
|
+
"reports.settings.pin": "P\xF4r no painel",
|
|
629
|
+
"reports.settings.unpin": "Tirar do painel",
|
|
630
|
+
"reports.settings.up": "Subir",
|
|
631
|
+
"reports.settings.down": "Descer",
|
|
632
|
+
"reports.settings.edit": "Ajustar",
|
|
633
|
+
"reports.settings.remove": "Remover do cat\xE1logo",
|
|
634
|
+
"reports.settings.restore": "Devolver",
|
|
635
|
+
"reports.settings.delete": "Excluir",
|
|
636
|
+
"reports.settings.width": "Largura",
|
|
637
|
+
"reports.settings.width.1": "Um quarto",
|
|
638
|
+
"reports.settings.width.2": "Metade",
|
|
639
|
+
"reports.settings.width.3": "Tr\xEAs quartos",
|
|
640
|
+
"reports.settings.width.4": "Inteiro",
|
|
641
|
+
"reports.settings.name": "Nome",
|
|
642
|
+
"reports.settings.category": "Categoria",
|
|
643
|
+
"reports.settings.source": "Fonte de dados",
|
|
644
|
+
"reports.settings.sourceHint": "De onde v\xEAm as linhas \u2014 \xE9 ela que decide quais campos voc\xEA pode usar.",
|
|
645
|
+
"reports.settings.viz": "Visualiza\xE7\xE3o",
|
|
646
|
+
"reports.settings.dimensions": "Agrupar por",
|
|
647
|
+
"reports.settings.measures": "Medidas",
|
|
648
|
+
"reports.settings.resetCard": "Restaurar este relat\xF3rio",
|
|
649
|
+
"reports.settings.needName": "D\xEA um nome ao relat\xF3rio.",
|
|
650
|
+
"reports.settings.needMeasure": "Escolha ao menos uma medida.",
|
|
651
|
+
"reports.settings.save": "Criar",
|
|
652
|
+
"reports.settings.cancel": "Cancelar",
|
|
653
|
+
"reports.settings.done": "Pronto",
|
|
654
|
+
"reports.viz.kpi": "N\xFAmero",
|
|
655
|
+
"reports.viz.line": "Linha",
|
|
656
|
+
"reports.viz.bar": "Barras",
|
|
657
|
+
"reports.viz.area": "\xC1rea",
|
|
658
|
+
"reports.viz.donut": "Rosca",
|
|
659
|
+
"reports.viz.breakdown": "Ranking",
|
|
660
|
+
"reports.viz.table": "Tabela",
|
|
661
|
+
"reports.viz.funnel": "Funil",
|
|
538
662
|
"reports.unavailableTitle": "Em breve",
|
|
539
663
|
"reports.unavailableDescription": "Este relat\xF3rio ainda n\xE3o est\xE1 dispon\xEDvel. Ser\xE1 habilitado em uma atualiza\xE7\xE3o futura."
|
|
540
664
|
};
|
|
@@ -625,8 +749,8 @@ function markViewed(cardId) {
|
|
|
625
749
|
for (const listener of listeners) listener(next);
|
|
626
750
|
}
|
|
627
751
|
function useLastViewed() {
|
|
628
|
-
const [map, setMap] =
|
|
629
|
-
|
|
752
|
+
const [map, setMap] = React10.useState(() => read());
|
|
753
|
+
React10.useEffect(() => {
|
|
630
754
|
listeners.add(setMap);
|
|
631
755
|
return () => {
|
|
632
756
|
listeners.delete(setMap);
|
|
@@ -634,27 +758,221 @@ function useLastViewed() {
|
|
|
634
758
|
}, []);
|
|
635
759
|
return map;
|
|
636
760
|
}
|
|
761
|
+
var KEY2 = "fayz.analytics.prefs";
|
|
762
|
+
var EMPTY = { version: 1, cards: {}, custom: [] };
|
|
763
|
+
function read2() {
|
|
764
|
+
if (typeof window === "undefined") return EMPTY;
|
|
765
|
+
try {
|
|
766
|
+
const raw = window.localStorage.getItem(KEY2);
|
|
767
|
+
if (!raw) return EMPTY;
|
|
768
|
+
const parsed = JSON.parse(raw);
|
|
769
|
+
return {
|
|
770
|
+
version: 1,
|
|
771
|
+
cards: parsed.cards && typeof parsed.cards === "object" ? parsed.cards : {},
|
|
772
|
+
custom: Array.isArray(parsed.custom) ? parsed.custom : []
|
|
773
|
+
};
|
|
774
|
+
} catch {
|
|
775
|
+
return EMPTY;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
function write2(prefs) {
|
|
779
|
+
if (typeof window === "undefined") return;
|
|
780
|
+
try {
|
|
781
|
+
const empty = Object.keys(prefs.cards).length === 0 && prefs.custom.length === 0;
|
|
782
|
+
if (empty) window.localStorage.removeItem(KEY2);
|
|
783
|
+
else window.localStorage.setItem(KEY2, JSON.stringify(prefs));
|
|
784
|
+
} catch {
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
var listeners2 = /* @__PURE__ */ new Set();
|
|
788
|
+
function publish(next) {
|
|
789
|
+
write2(next);
|
|
790
|
+
for (const listener of listeners2) listener(next);
|
|
791
|
+
}
|
|
792
|
+
function useAnalyticsPrefs() {
|
|
793
|
+
const [prefs, setPrefs] = React10.useState(() => read2());
|
|
794
|
+
React10.useEffect(() => {
|
|
795
|
+
listeners2.add(setPrefs);
|
|
796
|
+
return () => {
|
|
797
|
+
listeners2.delete(setPrefs);
|
|
798
|
+
};
|
|
799
|
+
}, []);
|
|
800
|
+
return prefs;
|
|
801
|
+
}
|
|
802
|
+
function setCardPref(cardId, patch) {
|
|
803
|
+
const current = read2();
|
|
804
|
+
const merged = { ...current.cards[cardId], ...patch };
|
|
805
|
+
const meaningful = Object.entries(merged).filter(([, v]) => v !== void 0 && v !== null);
|
|
806
|
+
const cards = { ...current.cards };
|
|
807
|
+
if (meaningful.length === 0) delete cards[cardId];
|
|
808
|
+
else cards[cardId] = Object.fromEntries(meaningful);
|
|
809
|
+
publish({ ...current, cards });
|
|
810
|
+
}
|
|
811
|
+
function clearCardPref(cardId) {
|
|
812
|
+
const current = read2();
|
|
813
|
+
const cards = { ...current.cards };
|
|
814
|
+
delete cards[cardId];
|
|
815
|
+
publish({ ...current, cards });
|
|
816
|
+
}
|
|
817
|
+
function setOverviewOrder(ids) {
|
|
818
|
+
const current = read2();
|
|
819
|
+
const cards = { ...current.cards };
|
|
820
|
+
ids.forEach((id, index) => {
|
|
821
|
+
cards[id] = { ...cards[id], order: index, pinned: cards[id]?.pinned ?? true };
|
|
822
|
+
});
|
|
823
|
+
publish({ ...current, cards });
|
|
824
|
+
}
|
|
825
|
+
function saveCustomReport(report) {
|
|
826
|
+
const current = read2();
|
|
827
|
+
const custom = current.custom.some((r) => r.id === report.id) ? current.custom.map((r) => r.id === report.id ? report : r) : [...current.custom, report];
|
|
828
|
+
publish({ ...current, custom });
|
|
829
|
+
}
|
|
830
|
+
function deleteCustomReport(id) {
|
|
831
|
+
const current = read2();
|
|
832
|
+
const cards = { ...current.cards };
|
|
833
|
+
delete cards[id];
|
|
834
|
+
publish({ ...current, cards, custom: current.custom.filter((r) => r.id !== id) });
|
|
835
|
+
}
|
|
836
|
+
function resetAnalyticsPrefs() {
|
|
837
|
+
publish({ ...EMPTY });
|
|
838
|
+
}
|
|
839
|
+
function sourceKey(card) {
|
|
840
|
+
const source = card.query.source;
|
|
841
|
+
if (source.kind === "fn") return card.grain ? `fn:${card.grain}` : `fn:${card.id}`;
|
|
842
|
+
return `${source.kind}:${source.name ?? card.id}`;
|
|
843
|
+
}
|
|
844
|
+
function analyticsSources(pool) {
|
|
845
|
+
const groups = /* @__PURE__ */ new Map();
|
|
846
|
+
for (const card of pool) {
|
|
847
|
+
const key = sourceKey(card);
|
|
848
|
+
const list = groups.get(key);
|
|
849
|
+
if (list) list.push(card);
|
|
850
|
+
else groups.set(key, [card]);
|
|
851
|
+
}
|
|
852
|
+
const out = [];
|
|
853
|
+
for (const [key, cards] of groups) {
|
|
854
|
+
const base = cards.find((c) => (c.query.measures?.length ?? 0) > 0);
|
|
855
|
+
if (!base) continue;
|
|
856
|
+
const palette = fieldPalette(pool, base);
|
|
857
|
+
if (palette.measures.length === 0) continue;
|
|
858
|
+
out.push({ key, base, label: base.name, ...palette });
|
|
859
|
+
}
|
|
860
|
+
return out;
|
|
861
|
+
}
|
|
862
|
+
function fieldPalette(pool, base) {
|
|
863
|
+
const key = sourceKey(base);
|
|
864
|
+
const dimensions = /* @__PURE__ */ new Map();
|
|
865
|
+
const measures = /* @__PURE__ */ new Map();
|
|
866
|
+
for (const card of pool) {
|
|
867
|
+
if (sourceKey(card) !== key) continue;
|
|
868
|
+
for (const d of card.query.dimensions ?? []) {
|
|
869
|
+
if (!d.hidden && !dimensions.has(d.key)) dimensions.set(d.key, d);
|
|
870
|
+
}
|
|
871
|
+
for (const m of card.query.measures ?? []) {
|
|
872
|
+
if (!measures.has(m.key)) measures.set(m.key, m);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
return { dimensions: [...dimensions.values()], measures: [...measures.values()] };
|
|
876
|
+
}
|
|
877
|
+
function withRatioOperands(kept, all) {
|
|
878
|
+
const set = new Set(kept);
|
|
879
|
+
for (const key of kept) {
|
|
880
|
+
const measure = all.find((m) => m.key === key);
|
|
881
|
+
for (const operand of measure?.ratioOf ?? []) set.add(operand);
|
|
882
|
+
}
|
|
883
|
+
return set;
|
|
884
|
+
}
|
|
885
|
+
function applyPref(card, pref) {
|
|
886
|
+
if (!pref) return card;
|
|
887
|
+
const next = { ...card, query: { ...card.query }, viz: { ...card.viz } };
|
|
888
|
+
if (pref.name) next.name = pref.name;
|
|
889
|
+
if (pref.viz) next.viz.type = pref.viz;
|
|
890
|
+
if (pref.measures?.length) {
|
|
891
|
+
const all = card.query.measures ?? [];
|
|
892
|
+
const keep = withRatioOperands(pref.measures, all);
|
|
893
|
+
next.query.measures = all.filter((m) => keep.has(m.key));
|
|
894
|
+
if (card.viz.measures?.length) {
|
|
895
|
+
next.viz.measures = card.viz.measures.filter((k) => keep.has(k));
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
if (pref.dimensions?.length) {
|
|
899
|
+
const keep = new Set(pref.dimensions);
|
|
900
|
+
next.query.dimensions = (card.query.dimensions ?? []).filter((d) => d.hidden || keep.has(d.key));
|
|
901
|
+
}
|
|
902
|
+
const placed = pref.pinned ?? Boolean(card.dashboard && card.dashboard.defaultVisible !== false);
|
|
903
|
+
if (!placed) {
|
|
904
|
+
next.dashboard = void 0;
|
|
905
|
+
} else {
|
|
906
|
+
next.dashboard = {
|
|
907
|
+
...card.dashboard,
|
|
908
|
+
defaultVisible: true,
|
|
909
|
+
order: pref.order ?? card.dashboard?.order ?? 999,
|
|
910
|
+
span: pref.span ?? card.dashboard?.span
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
return next;
|
|
914
|
+
}
|
|
915
|
+
function resolveCustomReport(report, pool) {
|
|
916
|
+
const base = pool.find((c) => c.id === report.baseId);
|
|
917
|
+
if (!base) return null;
|
|
918
|
+
const palette = fieldPalette(pool, base);
|
|
919
|
+
const dimensions = report.dimensions.map((key) => palette.dimensions.find((d) => d.key === key)).filter((d) => Boolean(d));
|
|
920
|
+
const keep = withRatioOperands(report.measures, palette.measures);
|
|
921
|
+
const measures = palette.measures.filter((m) => keep.has(m.key));
|
|
922
|
+
return {
|
|
923
|
+
id: report.id,
|
|
924
|
+
name: report.name,
|
|
925
|
+
description: report.description,
|
|
926
|
+
icon: report.icon ?? "FileText",
|
|
927
|
+
category: report.category,
|
|
928
|
+
createdBy: report.createdBy,
|
|
929
|
+
grain: base.grain,
|
|
930
|
+
available: base.available,
|
|
931
|
+
compare: base.compare,
|
|
932
|
+
permission: base.permission,
|
|
933
|
+
query: {
|
|
934
|
+
source: base.query.source,
|
|
935
|
+
dateColumn: base.query.dateColumn,
|
|
936
|
+
dimensions,
|
|
937
|
+
measures,
|
|
938
|
+
filters: base.query.filters,
|
|
939
|
+
defaultSort: report.measures[0] ?? dimensions[0]?.key,
|
|
940
|
+
defaultSortDir: "desc"
|
|
941
|
+
},
|
|
942
|
+
viz: { type: report.viz }
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
function resolveCards(catalogue, prefs) {
|
|
946
|
+
const custom = prefs.custom.map((report) => resolveCustomReport(report, catalogue)).filter((c) => Boolean(c));
|
|
947
|
+
return [...catalogue, ...custom].filter((card) => !prefs.cards[card.id]?.hidden).map((card) => applyPref(card, prefs.cards[card.id]));
|
|
948
|
+
}
|
|
637
949
|
var ANALYTICS_BASE = "/analytics";
|
|
638
950
|
var REPORTS_BASE = `${ANALYTICS_BASE}/reports`;
|
|
639
|
-
var
|
|
951
|
+
var SETTINGS_ROUTE = `${ANALYTICS_BASE}/settings`;
|
|
952
|
+
var ConfigContext = React10__default.createContext(null);
|
|
640
953
|
function useAnalyticsConfig() {
|
|
641
|
-
const ctx =
|
|
954
|
+
const ctx = React10__default.useContext(ConfigContext);
|
|
642
955
|
if (!ctx) throw new Error("useAnalyticsConfig must be used inside <AnalyticsSurface>");
|
|
643
956
|
return ctx;
|
|
644
957
|
}
|
|
645
|
-
function
|
|
958
|
+
function useAnalyticsCatalogue() {
|
|
646
959
|
const runtime = usePluginRuntime();
|
|
647
960
|
const { cards, createdBy } = useAnalyticsConfig();
|
|
648
|
-
return
|
|
961
|
+
return React10__default.useMemo(() => {
|
|
649
962
|
const byId = /* @__PURE__ */ new Map();
|
|
650
963
|
for (const card of getAnalyticsCards(runtime)) byId.set(card.id, card);
|
|
651
964
|
for (const card of cards) byId.set(card.id, card);
|
|
652
965
|
return [...byId.values()].map((c) => c.createdBy ? c : { ...c, createdBy });
|
|
653
966
|
}, [runtime, cards, createdBy]);
|
|
654
967
|
}
|
|
968
|
+
function useAnalyticsCards() {
|
|
969
|
+
const catalogue = useAnalyticsCatalogue();
|
|
970
|
+
const prefs = useAnalyticsPrefs();
|
|
971
|
+
return React10__default.useMemo(() => resolveCards(catalogue, prefs), [catalogue, prefs]);
|
|
972
|
+
}
|
|
655
973
|
function useOverviewCards() {
|
|
656
974
|
const all = useAnalyticsCards();
|
|
657
|
-
return
|
|
975
|
+
return React10__default.useMemo(
|
|
658
976
|
() => all.filter((c) => c.dashboard && c.dashboard.defaultVisible !== false).sort((a, b) => (a.dashboard?.order ?? 0) - (b.dashboard?.order ?? 0)),
|
|
659
977
|
[all]
|
|
660
978
|
);
|
|
@@ -685,7 +1003,7 @@ function navigateAnalytics(path) {
|
|
|
685
1003
|
hashRouterAdapter().navigate(path);
|
|
686
1004
|
}
|
|
687
1005
|
function AnalyticsSurface({ config, provider, children }) {
|
|
688
|
-
const openCard =
|
|
1006
|
+
const openCard = React10__default.useCallback((card) => {
|
|
689
1007
|
markViewed(card.id);
|
|
690
1008
|
navigateAnalytics(`${REPORTS_BASE}/${encodeURIComponent(card.id)}`);
|
|
691
1009
|
}, []);
|
|
@@ -706,7 +1024,7 @@ function AnalyticsSurface({ config, provider, children }) {
|
|
|
706
1024
|
function AnalyticsAssistant() {
|
|
707
1025
|
const { assistant } = useAnalyticsConfig();
|
|
708
1026
|
const t = useTranslation();
|
|
709
|
-
const suggestions =
|
|
1027
|
+
const suggestions = React10__default.useMemo(
|
|
710
1028
|
() => assistant?.suggestions ?? [0, 1, 2].map((i) => ({ label: t(`reports.assistant.suggestion.${i}`) })),
|
|
711
1029
|
[assistant?.suggestions, t]
|
|
712
1030
|
);
|
|
@@ -716,6 +1034,7 @@ function AnalyticsAssistant() {
|
|
|
716
1034
|
{
|
|
717
1035
|
id: "analytics",
|
|
718
1036
|
auto: assistant.auto ?? false,
|
|
1037
|
+
role: assistant.role ?? "growth",
|
|
719
1038
|
persona: assistant.persona ?? t("reports.assistant.persona"),
|
|
720
1039
|
headline: assistant.headline ?? t("reports.assistant.headline"),
|
|
721
1040
|
pitch: assistant.pitch ?? t("reports.assistant.pitch"),
|
|
@@ -739,7 +1058,7 @@ function useAnalyticsTree() {
|
|
|
739
1058
|
const t = useTranslation();
|
|
740
1059
|
const { labels, showLiveView } = useAnalyticsConfig();
|
|
741
1060
|
const cards = useAnalyticsCards();
|
|
742
|
-
return
|
|
1061
|
+
return React10__default.useMemo(() => ({
|
|
743
1062
|
title: labels.analytics,
|
|
744
1063
|
// Only the overview. The searchable catalogue (/analytics/reports) is still
|
|
745
1064
|
// routed — bookmarks and the legacy /reports link resolve — but it is not
|
|
@@ -749,7 +1068,16 @@ function useAnalyticsTree() {
|
|
|
749
1068
|
{ id: "overview", label: labels.overview ?? t("reports.overview"), icon: "BarChart3", route: ANALYTICS_BASE, view: "overview" }
|
|
750
1069
|
],
|
|
751
1070
|
groups: groupCards(cards),
|
|
752
|
-
live: showLiveView ? { id: "live", label: labels.liveView, icon: "Radio", route: `${ANALYTICS_BASE}/live`, view: "live" } : null
|
|
1071
|
+
live: showLiveView ? { id: "live", label: labels.liveView, icon: "Radio", route: `${ANALYTICS_BASE}/live`, view: "live" } : null,
|
|
1072
|
+
// Depois dos relatórios, não antes: é a tela que fala SOBRE a lista, e uma
|
|
1073
|
+
// engrenagem no topo empurra para baixo o que a pessoa veio ver.
|
|
1074
|
+
settings: {
|
|
1075
|
+
id: "settings",
|
|
1076
|
+
label: labels.settings ?? t("reports.settings.nav"),
|
|
1077
|
+
icon: "SlidersHorizontal",
|
|
1078
|
+
route: SETTINGS_ROUTE,
|
|
1079
|
+
view: "settings"
|
|
1080
|
+
}
|
|
753
1081
|
}), [labels, showLiveView, cards, t]);
|
|
754
1082
|
}
|
|
755
1083
|
function AnalyticsModule({ view, cardId, children }) {
|
|
@@ -758,7 +1086,7 @@ function AnalyticsModule({ view, cardId, children }) {
|
|
|
758
1086
|
const activeId = cardId ? decodeURIComponent(cardId) : void 0;
|
|
759
1087
|
const shellRail = useShellRailAvailable();
|
|
760
1088
|
const claimsRail = shellRail ?? variant !== "rail";
|
|
761
|
-
const entries =
|
|
1089
|
+
const entries = React10__default.useMemo(() => [
|
|
762
1090
|
...tree.fixed.map((f) => ({
|
|
763
1091
|
id: `analytics:${f.id}`,
|
|
764
1092
|
label: f.label,
|
|
@@ -782,10 +1110,17 @@ function AnalyticsModule({ view, cardId, children }) {
|
|
|
782
1110
|
icon: tree.live.icon,
|
|
783
1111
|
route: tree.live.route,
|
|
784
1112
|
section: "main"
|
|
785
|
-
}] : []
|
|
1113
|
+
}] : [],
|
|
1114
|
+
{
|
|
1115
|
+
id: "analytics:settings",
|
|
1116
|
+
label: tree.settings.label,
|
|
1117
|
+
icon: tree.settings.icon,
|
|
1118
|
+
route: tree.settings.route,
|
|
1119
|
+
section: "main"
|
|
1120
|
+
}
|
|
786
1121
|
], [tree]);
|
|
787
1122
|
useContextualNav(claimsRail ? { base: ANALYTICS_BASE, entries, label: tree.title } : null);
|
|
788
|
-
const nav =
|
|
1123
|
+
const nav = React10__default.useMemo(() => {
|
|
789
1124
|
if (claimsRail) return [];
|
|
790
1125
|
return [
|
|
791
1126
|
...tree.fixed.map((f) => ({
|
|
@@ -811,7 +1146,14 @@ function AnalyticsModule({ view, cardId, children }) {
|
|
|
811
1146
|
icon: tree.live.icon,
|
|
812
1147
|
active: view === "live",
|
|
813
1148
|
onClick: () => navigateAnalytics(tree.live.route)
|
|
814
|
-
}] : []
|
|
1149
|
+
}] : [],
|
|
1150
|
+
{
|
|
1151
|
+
id: tree.settings.id,
|
|
1152
|
+
label: tree.settings.label,
|
|
1153
|
+
icon: tree.settings.icon,
|
|
1154
|
+
active: view === "settings",
|
|
1155
|
+
onClick: () => navigateAnalytics(tree.settings.route)
|
|
1156
|
+
}
|
|
815
1157
|
];
|
|
816
1158
|
}, [tree, view, activeId, claimsRail]);
|
|
817
1159
|
return /* @__PURE__ */ jsxs(
|
|
@@ -830,15 +1172,11 @@ function AnalyticsModule({ view, cardId, children }) {
|
|
|
830
1172
|
);
|
|
831
1173
|
}
|
|
832
1174
|
function AnalyticsOverview() {
|
|
833
|
-
const { labels
|
|
1175
|
+
const { labels } = useAnalyticsConfig();
|
|
834
1176
|
const cards = useOverviewCards();
|
|
835
|
-
const filters =
|
|
1177
|
+
const filters = React10__default.useMemo(() => collectFilters(cards), [cards]);
|
|
836
1178
|
return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
837
|
-
|
|
838
|
-
/* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold tracking-tight", children: labels.analytics }),
|
|
839
|
-
/* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: labels.overviewSubtitle })
|
|
840
|
-
] }) : null,
|
|
841
|
-
/* @__PURE__ */ jsx(AnalyticsControls, { filters, className: "sticky top-3 z-20" }),
|
|
1179
|
+
/* @__PURE__ */ jsx(AnalyticsControls, { filters }),
|
|
842
1180
|
cards.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center rounded-xl border border-dashed py-16 text-center", children: [
|
|
843
1181
|
/* @__PURE__ */ jsx("div", { className: "mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-muted", children: /* @__PURE__ */ jsx(BarChart3, { className: "h-5 w-5 text-muted-foreground" }) }),
|
|
844
1182
|
/* @__PURE__ */ jsx("p", { className: "font-medium", children: labels.emptyOverviewTitle }),
|
|
@@ -856,18 +1194,18 @@ function ReportsListPage() {
|
|
|
856
1194
|
const { labels, currency, showHeader } = useAnalyticsConfig();
|
|
857
1195
|
const cards = useAnalyticsCards();
|
|
858
1196
|
const lastViewed = useLastViewed();
|
|
859
|
-
const [search, setSearch] =
|
|
860
|
-
const [category, setCategory] =
|
|
861
|
-
const [author, setAuthor] =
|
|
862
|
-
const categoryOptions =
|
|
1197
|
+
const [search, setSearch] = React10__default.useState("");
|
|
1198
|
+
const [category, setCategory] = React10__default.useState();
|
|
1199
|
+
const [author, setAuthor] = React10__default.useState();
|
|
1200
|
+
const categoryOptions = React10__default.useMemo(
|
|
863
1201
|
() => [...new Set(cards.map((c) => c.category))].sort((a, b) => a.localeCompare(b)).map((c) => ({ value: c, label: c })),
|
|
864
1202
|
[cards]
|
|
865
1203
|
);
|
|
866
|
-
const authorOptions =
|
|
1204
|
+
const authorOptions = React10__default.useMemo(
|
|
867
1205
|
() => [...new Set(cards.map((c) => c.createdBy ?? "\u2014"))].sort((a, b) => a.localeCompare(b)).map((a) => ({ value: a, label: a })),
|
|
868
1206
|
[cards]
|
|
869
1207
|
);
|
|
870
|
-
const rows =
|
|
1208
|
+
const rows = React10__default.useMemo(() => {
|
|
871
1209
|
const q = search.trim().toLowerCase();
|
|
872
1210
|
return cards.filter((card) => {
|
|
873
1211
|
if (category && card.category !== category) return false;
|
|
@@ -876,7 +1214,7 @@ function ReportsListPage() {
|
|
|
876
1214
|
return card.name.toLowerCase().includes(q) || card.description?.toLowerCase().includes(q) || card.category.toLowerCase().includes(q);
|
|
877
1215
|
});
|
|
878
1216
|
}, [cards, search, category, author]);
|
|
879
|
-
const columns =
|
|
1217
|
+
const columns = React10__default.useMemo(() => [
|
|
880
1218
|
{
|
|
881
1219
|
id: "name",
|
|
882
1220
|
accessorFn: (card) => card.name,
|
|
@@ -996,25 +1334,22 @@ function ExportDropdown({ columns, data, filename, currency, title }) {
|
|
|
996
1334
|
const [excelAvailable, setExcelAvailable] = useState(false);
|
|
997
1335
|
const [pdfAvailable, setPdfAvailable] = useState(false);
|
|
998
1336
|
useEffect(() => {
|
|
999
|
-
import('./excel-
|
|
1337
|
+
import('./excel-LDM5H5IW.js').then((m) => m.isExcelAvailable()).then(setExcelAvailable).catch(() => {
|
|
1000
1338
|
});
|
|
1001
|
-
import('./pdf-
|
|
1339
|
+
import('./pdf-XA2AR6B4.js').then((m) => m.isPdfAvailable()).then(setPdfAvailable).catch(() => {
|
|
1002
1340
|
});
|
|
1003
1341
|
}, []);
|
|
1004
1342
|
const handleCsv = () => exportToCsv(columns, data, filename, currency);
|
|
1005
1343
|
const handleExcel = async () => {
|
|
1006
|
-
const { exportToExcel } = await import('./excel-
|
|
1344
|
+
const { exportToExcel } = await import('./excel-LDM5H5IW.js');
|
|
1007
1345
|
await exportToExcel(columns, data, filename, currency);
|
|
1008
1346
|
};
|
|
1009
1347
|
const handlePdf = async () => {
|
|
1010
|
-
const { exportToPdf } = await import('./pdf-
|
|
1348
|
+
const { exportToPdf } = await import('./pdf-XA2AR6B4.js');
|
|
1011
1349
|
await exportToPdf(columns, data, filename, { title, currency });
|
|
1012
1350
|
};
|
|
1013
1351
|
return /* @__PURE__ */ jsxs(Dropdown, { children: [
|
|
1014
|
-
/* @__PURE__ */ jsx(DropdownTrigger, { asChild: true, children: /* @__PURE__ */
|
|
1015
|
-
/* @__PURE__ */ jsx(Download, { className: "h-4 w-4 mr-1.5" }),
|
|
1016
|
-
t("reports.export")
|
|
1017
|
-
] }) }),
|
|
1352
|
+
/* @__PURE__ */ jsx(DropdownTrigger, { asChild: true, children: /* @__PURE__ */ jsx(ToolbarAction, { icon: Download, caret: true, children: t("reports.export") }) }),
|
|
1018
1353
|
/* @__PURE__ */ jsxs(DropdownContent, { align: "end", children: [
|
|
1019
1354
|
/* @__PURE__ */ jsx(DropdownItem, { onClick: handleCsv, children: t("reports.exportCsv") }),
|
|
1020
1355
|
excelAvailable && /* @__PURE__ */ jsx(DropdownItem, { onClick: handleExcel, children: t("reports.exportExcel") }),
|
|
@@ -1023,11 +1358,12 @@ function ExportDropdown({ columns, data, filename, currency, title }) {
|
|
|
1023
1358
|
] });
|
|
1024
1359
|
}
|
|
1025
1360
|
function ReportPagination({ page: page2, pageSize, total, onPageChange, onPageSizeChange }) {
|
|
1361
|
+
const t = useTranslation();
|
|
1026
1362
|
const totalPages = Math.max(1, Math.ceil(total / pageSize));
|
|
1027
1363
|
const from = (page2 - 1) * pageSize + 1;
|
|
1028
1364
|
const to = Math.min(page2 * pageSize, total);
|
|
1029
1365
|
return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-t px-4 py-3", children: [
|
|
1030
|
-
/* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: total > 0 ?
|
|
1366
|
+
/* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: total > 0 ? t("reports.pagination.range", { from, to, total }) : t("reports.pagination.empty") }),
|
|
1031
1367
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
1032
1368
|
/* @__PURE__ */ jsx(
|
|
1033
1369
|
"select",
|
|
@@ -1035,10 +1371,7 @@ function ReportPagination({ page: page2, pageSize, total, onPageChange, onPageSi
|
|
|
1035
1371
|
value: pageSize,
|
|
1036
1372
|
onChange: (e) => onPageSizeChange(Number(e.target.value)),
|
|
1037
1373
|
className: "h-8 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-2 text-sm",
|
|
1038
|
-
children: [25, 50, 100].map((size) => /* @__PURE__ */
|
|
1039
|
-
size,
|
|
1040
|
-
" / page"
|
|
1041
|
-
] }, size))
|
|
1374
|
+
children: [25, 50, 100].map((size) => /* @__PURE__ */ jsx("option", { value: size, children: t("reports.pagination.perPage", { size }) }, size))
|
|
1042
1375
|
}
|
|
1043
1376
|
),
|
|
1044
1377
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
@@ -1075,8 +1408,8 @@ function ViewerBody({ card }) {
|
|
|
1075
1408
|
const { labels, currency } = useAnalyticsConfig();
|
|
1076
1409
|
const { state, setPage, setPageSize } = useAnalyticsContext();
|
|
1077
1410
|
const { result, loading, error, engineMissing } = useAnalyticsCard(card);
|
|
1078
|
-
const columns =
|
|
1079
|
-
const tanstackColumns =
|
|
1411
|
+
const columns = React10__default.useMemo(() => cardColumns(card), [card]);
|
|
1412
|
+
const tanstackColumns = React10__default.useMemo(
|
|
1080
1413
|
() => reportColumnsToTanstack(columns, currency.code),
|
|
1081
1414
|
[columns, currency.code]
|
|
1082
1415
|
);
|
|
@@ -1133,7 +1466,7 @@ function AnalyticsReportViewer({ cardId }) {
|
|
|
1133
1466
|
const { labels } = useAnalyticsConfig();
|
|
1134
1467
|
const cards = useAnalyticsCards();
|
|
1135
1468
|
const card = cards.find((c) => c.id === decodeURIComponent(cardId ?? ""));
|
|
1136
|
-
|
|
1469
|
+
React10__default.useEffect(() => {
|
|
1137
1470
|
if (card) markViewed(card.id);
|
|
1138
1471
|
}, [card?.id]);
|
|
1139
1472
|
if (!card) {
|
|
@@ -1168,9 +1501,9 @@ function AnalyticsReportViewer({ cardId }) {
|
|
|
1168
1501
|
] });
|
|
1169
1502
|
}
|
|
1170
1503
|
function ViewerActions({ card }) {
|
|
1171
|
-
const { currency } = useAnalyticsConfig();
|
|
1504
|
+
const { currency, labels } = useAnalyticsConfig();
|
|
1172
1505
|
const { result } = useAnalyticsCard(card);
|
|
1173
|
-
const columns =
|
|
1506
|
+
const columns = React10__default.useMemo(() => cardColumns(card), [card]);
|
|
1174
1507
|
if (!result?.rows?.length) return null;
|
|
1175
1508
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1176
1509
|
/* @__PURE__ */ jsx(
|
|
@@ -1183,10 +1516,7 @@ function ViewerActions({ card }) {
|
|
|
1183
1516
|
title: card.name
|
|
1184
1517
|
}
|
|
1185
1518
|
),
|
|
1186
|
-
/* @__PURE__ */
|
|
1187
|
-
/* @__PURE__ */ jsx(Printer, { className: "mr-1.5 h-3.5 w-3.5" }),
|
|
1188
|
-
"Print"
|
|
1189
|
-
] })
|
|
1519
|
+
/* @__PURE__ */ jsx(ToolbarAction, { icon: Printer, onClick: () => window.print(), children: labels.print })
|
|
1190
1520
|
] });
|
|
1191
1521
|
}
|
|
1192
1522
|
function LiveViewPage() {
|
|
@@ -1197,6 +1527,571 @@ function LiveViewPage() {
|
|
|
1197
1527
|
/* @__PURE__ */ jsx("p", { className: "mt-1 max-w-sm text-sm text-muted-foreground", children: labels.liveViewSoon })
|
|
1198
1528
|
] });
|
|
1199
1529
|
}
|
|
1530
|
+
var VIZ_TYPES = ["kpi", "line", "bar", "area", "donut", "breakdown", "table"];
|
|
1531
|
+
function vizOptionsFor(card) {
|
|
1532
|
+
const hasDimension = (card.query.dimensions ?? []).some((d) => !d.hidden);
|
|
1533
|
+
return hasDimension ? VIZ_TYPES : ["kpi", "table"];
|
|
1534
|
+
}
|
|
1535
|
+
function CardIcon({ name, className }) {
|
|
1536
|
+
const Icon = getLucideIcon(name ?? "FileText") ?? getLucideIcon("FileText");
|
|
1537
|
+
return Icon ? /* @__PURE__ */ jsx(Icon, { className }) : null;
|
|
1538
|
+
}
|
|
1539
|
+
function ReportRow({ card, custom, onEdit, children, drag }) {
|
|
1540
|
+
const t = useTranslation();
|
|
1541
|
+
return /* @__PURE__ */ jsxs(
|
|
1542
|
+
"li",
|
|
1543
|
+
{
|
|
1544
|
+
draggable: Boolean(drag),
|
|
1545
|
+
onDragStart: drag?.onDragStart,
|
|
1546
|
+
onDragOver: drag?.onDragOver,
|
|
1547
|
+
onDragEnd: drag?.onDragEnd,
|
|
1548
|
+
className: [
|
|
1549
|
+
"flex items-center gap-2 border-b border-border/60 px-3 py-2 last:border-0",
|
|
1550
|
+
drag ? "cursor-grab active:cursor-grabbing" : "",
|
|
1551
|
+
drag?.dragging ? "opacity-40" : ""
|
|
1552
|
+
].join(" "),
|
|
1553
|
+
children: [
|
|
1554
|
+
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 }),
|
|
1555
|
+
/* @__PURE__ */ jsx(CardIcon, { name: card.icon, className: "h-4 w-4 shrink-0 text-muted-foreground" }),
|
|
1556
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
1557
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1558
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-sm font-medium", children: card.name }),
|
|
1559
|
+
custom ? /* @__PURE__ */ jsx(Badge, { variant: "secondary", children: t("reports.settings.custom") }) : null
|
|
1560
|
+
] }),
|
|
1561
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-xs text-muted-foreground", children: card.category })
|
|
1562
|
+
] }),
|
|
1563
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [
|
|
1564
|
+
children,
|
|
1565
|
+
/* @__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" }) })
|
|
1566
|
+
] })
|
|
1567
|
+
]
|
|
1568
|
+
}
|
|
1569
|
+
);
|
|
1570
|
+
}
|
|
1571
|
+
function CardEditor({ card, onClose }) {
|
|
1572
|
+
const t = useTranslation();
|
|
1573
|
+
const prefs = useAnalyticsPrefs();
|
|
1574
|
+
const pref = prefs.cards[card.id] ?? {};
|
|
1575
|
+
const isCustom = prefs.custom.some((r) => r.id === card.id);
|
|
1576
|
+
const [name, setName] = React10__default.useState(card.name);
|
|
1577
|
+
const measures = card.query.measures ?? [];
|
|
1578
|
+
const dimensions = (card.query.dimensions ?? []).filter((d) => !d.hidden);
|
|
1579
|
+
const keptMeasures = new Set(pref.measures ?? measures.map((m) => m.key));
|
|
1580
|
+
const keptDimensions = new Set(pref.dimensions ?? dimensions.map((d) => d.key));
|
|
1581
|
+
const toggle = (set, key, field, all) => {
|
|
1582
|
+
const next = new Set(set);
|
|
1583
|
+
if (next.has(key)) next.delete(key);
|
|
1584
|
+
else next.add(key);
|
|
1585
|
+
setCardPref(card.id, { [field]: next.size === all.length ? void 0 : [...next] });
|
|
1586
|
+
};
|
|
1587
|
+
return (
|
|
1588
|
+
// A coluna da direita, não uma gaveta só desta tela: ajustar um relatório é
|
|
1589
|
+
// olhar a lista enquanto se mexe nele, e uma gaveta cobre exatamente a
|
|
1590
|
+
// lista. É a mesma coluna do assistente — RightRailPage cai numa Sheet
|
|
1591
|
+
// sozinha quando a shell não tem coluna nenhuma.
|
|
1592
|
+
/* @__PURE__ */ jsxs(RightRailPage, { title: card.name, open: true, onClose, width: "max-w-md", children: [
|
|
1593
|
+
/* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto p-4", children: /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
|
|
1594
|
+
card.description ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: card.description }) : null,
|
|
1595
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
1596
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("reports.settings.name") }),
|
|
1597
|
+
/* @__PURE__ */ jsx(
|
|
1598
|
+
Input,
|
|
1599
|
+
{
|
|
1600
|
+
value: name,
|
|
1601
|
+
onChange: (e) => setName(e.target.value),
|
|
1602
|
+
onBlur: () => setCardPref(card.id, { name: name.trim() && name !== card.name ? name.trim() : void 0 })
|
|
1603
|
+
}
|
|
1604
|
+
)
|
|
1605
|
+
] }),
|
|
1606
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
1607
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("reports.settings.viz") }),
|
|
1608
|
+
/* @__PURE__ */ jsxs(
|
|
1609
|
+
Select,
|
|
1610
|
+
{
|
|
1611
|
+
value: card.viz.type,
|
|
1612
|
+
onValueChange: (value) => setCardPref(card.id, { viz: value }),
|
|
1613
|
+
children: [
|
|
1614
|
+
/* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
1615
|
+
/* @__PURE__ */ jsx(SelectContent, { children: vizOptionsFor(card).map((viz) => /* @__PURE__ */ jsx(SelectItem, { value: viz, children: t(`reports.viz.${viz}`) }, viz)) })
|
|
1616
|
+
]
|
|
1617
|
+
}
|
|
1618
|
+
)
|
|
1619
|
+
] }),
|
|
1620
|
+
dimensions.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1621
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground", children: t("reports.settings.dimensions") }),
|
|
1622
|
+
dimensions.map((d) => /* @__PURE__ */ jsxs("label", { className: "flex cursor-pointer items-center gap-2 text-sm", children: [
|
|
1623
|
+
/* @__PURE__ */ jsx(
|
|
1624
|
+
Checkbox,
|
|
1625
|
+
{
|
|
1626
|
+
checked: keptDimensions.has(d.key),
|
|
1627
|
+
onChange: () => toggle(keptDimensions, d.key, "dimensions", dimensions.map((x) => x.key))
|
|
1628
|
+
}
|
|
1629
|
+
),
|
|
1630
|
+
d.label
|
|
1631
|
+
] }, d.key))
|
|
1632
|
+
] }) : null,
|
|
1633
|
+
measures.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1634
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground", children: t("reports.settings.measures") }),
|
|
1635
|
+
measures.map((m) => /* @__PURE__ */ jsxs("label", { className: "flex cursor-pointer items-center gap-2 text-sm", children: [
|
|
1636
|
+
/* @__PURE__ */ jsx(
|
|
1637
|
+
Checkbox,
|
|
1638
|
+
{
|
|
1639
|
+
checked: keptMeasures.has(m.key),
|
|
1640
|
+
onChange: () => toggle(keptMeasures, m.key, "measures", measures.map((x) => x.key))
|
|
1641
|
+
}
|
|
1642
|
+
),
|
|
1643
|
+
m.label
|
|
1644
|
+
] }, m.key))
|
|
1645
|
+
] }) : null
|
|
1646
|
+
] }) }),
|
|
1647
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-t border-border p-3", children: [
|
|
1648
|
+
isCustom ? /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: () => {
|
|
1649
|
+
deleteCustomReport(card.id);
|
|
1650
|
+
onClose();
|
|
1651
|
+
}, children: [
|
|
1652
|
+
/* @__PURE__ */ jsx(Trash2, { className: "mr-1.5 h-3.5 w-3.5" }),
|
|
1653
|
+
t("reports.settings.delete")
|
|
1654
|
+
] }) : /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: () => {
|
|
1655
|
+
clearCardPref(card.id);
|
|
1656
|
+
onClose();
|
|
1657
|
+
}, children: [
|
|
1658
|
+
/* @__PURE__ */ jsx(RotateCcw, { className: "mr-1.5 h-3.5 w-3.5" }),
|
|
1659
|
+
t("reports.settings.resetCard")
|
|
1660
|
+
] }),
|
|
1661
|
+
/* @__PURE__ */ jsx(Button, { size: "sm", onClick: onClose, children: t("reports.settings.done") })
|
|
1662
|
+
] })
|
|
1663
|
+
] })
|
|
1664
|
+
);
|
|
1665
|
+
}
|
|
1666
|
+
function slugify(value) {
|
|
1667
|
+
return value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "report";
|
|
1668
|
+
}
|
|
1669
|
+
function buildCustomReport(input) {
|
|
1670
|
+
const dimensionKeys = new Set(input.source.dimensions.map((d) => d.key));
|
|
1671
|
+
const measureKeys = new Set(input.source.measures.map((m) => m.key));
|
|
1672
|
+
return {
|
|
1673
|
+
id: `custom.${slugify(input.name)}-${Date.now().toString(36)}`,
|
|
1674
|
+
name: input.name.trim(),
|
|
1675
|
+
category: input.category.trim() || "Geral",
|
|
1676
|
+
icon: "FileText",
|
|
1677
|
+
createdBy: input.createdBy,
|
|
1678
|
+
baseId: input.source.base.id,
|
|
1679
|
+
// Filtered, not trusted: a field the source does not have would resolve to
|
|
1680
|
+
// nothing and the report would open empty with no explanation.
|
|
1681
|
+
dimensions: input.dimensions.filter((k) => dimensionKeys.has(k)),
|
|
1682
|
+
measures: input.measures.filter((k) => measureKeys.has(k)),
|
|
1683
|
+
viz: input.viz,
|
|
1684
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1685
|
+
};
|
|
1686
|
+
}
|
|
1687
|
+
function NewReportPanel({
|
|
1688
|
+
sources,
|
|
1689
|
+
categories,
|
|
1690
|
+
onClose
|
|
1691
|
+
}) {
|
|
1692
|
+
const t = useTranslation();
|
|
1693
|
+
const { createdBy } = useAnalyticsConfig();
|
|
1694
|
+
const [name, setName] = React10__default.useState("");
|
|
1695
|
+
const [category, setCategory] = React10__default.useState(categories[0] ?? "Geral");
|
|
1696
|
+
const [sourceKeyState, setSourceKey] = React10__default.useState(sources[0]?.key ?? "");
|
|
1697
|
+
const [dimensions, setDimensions] = React10__default.useState([]);
|
|
1698
|
+
const [measures, setMeasures] = React10__default.useState([]);
|
|
1699
|
+
const [viz, setViz] = React10__default.useState("table");
|
|
1700
|
+
const source = sources.find((s) => s.key === sourceKeyState);
|
|
1701
|
+
React10__default.useEffect(() => {
|
|
1702
|
+
setDimensions([]);
|
|
1703
|
+
setMeasures([]);
|
|
1704
|
+
}, [sourceKeyState]);
|
|
1705
|
+
const toggle = (list, set, key) => {
|
|
1706
|
+
set(list.includes(key) ? list.filter((k) => k !== key) : [...list, key]);
|
|
1707
|
+
};
|
|
1708
|
+
const error = !name.trim() ? t("reports.settings.needName") : measures.length === 0 ? t("reports.settings.needMeasure") : null;
|
|
1709
|
+
const submit = () => {
|
|
1710
|
+
if (error || !source) return;
|
|
1711
|
+
saveCustomReport(buildCustomReport({
|
|
1712
|
+
name,
|
|
1713
|
+
category,
|
|
1714
|
+
createdBy,
|
|
1715
|
+
source,
|
|
1716
|
+
dimensions,
|
|
1717
|
+
measures,
|
|
1718
|
+
viz
|
|
1719
|
+
}));
|
|
1720
|
+
onClose();
|
|
1721
|
+
};
|
|
1722
|
+
return /* @__PURE__ */ jsxs(RightRailPage, { title: t("reports.settings.new"), open: true, onClose, width: "max-w-md", children: [
|
|
1723
|
+
/* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto p-4", children: /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
|
|
1724
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
1725
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("reports.settings.name") }),
|
|
1726
|
+
/* @__PURE__ */ jsx(Input, { value: name, onChange: (e) => setName(e.target.value) })
|
|
1727
|
+
] }),
|
|
1728
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
1729
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("reports.settings.category") }),
|
|
1730
|
+
/* @__PURE__ */ jsx(Input, { value: category, onChange: (e) => setCategory(e.target.value), list: "fayz-analytics-categories" }),
|
|
1731
|
+
/* @__PURE__ */ jsx("datalist", { id: "fayz-analytics-categories", children: categories.map((c) => /* @__PURE__ */ jsx("option", { value: c }, c)) })
|
|
1732
|
+
] }),
|
|
1733
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
1734
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("reports.settings.source") }),
|
|
1735
|
+
/* @__PURE__ */ jsxs(Select, { value: sourceKeyState, onValueChange: setSourceKey, children: [
|
|
1736
|
+
/* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
1737
|
+
/* @__PURE__ */ jsx(SelectContent, { children: sources.map((s) => /* @__PURE__ */ jsx(SelectItem, { value: s.key, children: s.label }, s.key)) })
|
|
1738
|
+
] }),
|
|
1739
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("reports.settings.sourceHint") })
|
|
1740
|
+
] }),
|
|
1741
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1742
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground", children: t("reports.settings.dimensions") }),
|
|
1743
|
+
(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: [
|
|
1744
|
+
/* @__PURE__ */ jsx(Checkbox, { checked: dimensions.includes(d.key), onChange: () => toggle(dimensions, setDimensions, d.key) }),
|
|
1745
|
+
d.label
|
|
1746
|
+
] }, d.key))
|
|
1747
|
+
] }),
|
|
1748
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1749
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground", children: t("reports.settings.measures") }),
|
|
1750
|
+
(source?.measures ?? []).map((m) => /* @__PURE__ */ jsxs("label", { className: "flex cursor-pointer items-center gap-2 text-sm", children: [
|
|
1751
|
+
/* @__PURE__ */ jsx(Checkbox, { checked: measures.includes(m.key), onChange: () => toggle(measures, setMeasures, m.key) }),
|
|
1752
|
+
m.label
|
|
1753
|
+
] }, m.key))
|
|
1754
|
+
] }),
|
|
1755
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
1756
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("reports.settings.viz") }),
|
|
1757
|
+
/* @__PURE__ */ jsxs(Select, { value: viz, onValueChange: (value) => setViz(value), children: [
|
|
1758
|
+
/* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
1759
|
+
/* @__PURE__ */ jsx(SelectContent, { children: (dimensions.length > 0 ? VIZ_TYPES : ["kpi", "table"]).map((v) => /* @__PURE__ */ jsx(SelectItem, { value: v, children: t(`reports.viz.${v}`) }, v)) })
|
|
1760
|
+
] })
|
|
1761
|
+
] })
|
|
1762
|
+
] }) }),
|
|
1763
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 border-t border-border p-3", children: [
|
|
1764
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-xs text-destructive", children: error }),
|
|
1765
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [
|
|
1766
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: onClose, children: t("reports.settings.cancel") }),
|
|
1767
|
+
/* @__PURE__ */ jsx(Button, { size: "sm", onClick: submit, disabled: Boolean(error), children: t("reports.settings.save") })
|
|
1768
|
+
] })
|
|
1769
|
+
] })
|
|
1770
|
+
] });
|
|
1771
|
+
}
|
|
1772
|
+
var CREATE_REPORT_TOOL = {
|
|
1773
|
+
id: "reports.createAnalyticsReport",
|
|
1774
|
+
name: "createAnalyticsReport",
|
|
1775
|
+
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.",
|
|
1776
|
+
icon: "FileText",
|
|
1777
|
+
mode: "persist",
|
|
1778
|
+
execution: { plane: "client" },
|
|
1779
|
+
category: "Analytics",
|
|
1780
|
+
suggestions: [
|
|
1781
|
+
{ label: "Um relat\xF3rio de receita por mesa" },
|
|
1782
|
+
{ label: "Quanto cada pra\xE7a produz por turno" }
|
|
1783
|
+
],
|
|
1784
|
+
parameters: {
|
|
1785
|
+
type: "object",
|
|
1786
|
+
properties: {
|
|
1787
|
+
name: { type: "string", description: "Report name, in the user's language." },
|
|
1788
|
+
source: { type: "string", description: "A `key` from the surface state's `sources`." },
|
|
1789
|
+
group_by: {
|
|
1790
|
+
type: "array",
|
|
1791
|
+
description: "Dimension keys from that source. Empty means a single headline number.",
|
|
1792
|
+
items: { type: "string" }
|
|
1793
|
+
},
|
|
1794
|
+
measures: {
|
|
1795
|
+
type: "array",
|
|
1796
|
+
description: "Measure keys from that source. At least one.",
|
|
1797
|
+
items: { type: "string" }
|
|
1798
|
+
},
|
|
1799
|
+
viz: {
|
|
1800
|
+
type: "string",
|
|
1801
|
+
enum: ["kpi", "line", "bar", "area", "donut", "breakdown", "table"],
|
|
1802
|
+
description: "How to draw it. Defaults to table."
|
|
1803
|
+
},
|
|
1804
|
+
category: { type: "string", description: "Group it sits under in the rail. Reuse an existing one when it fits." },
|
|
1805
|
+
pin: { type: "boolean", description: "Also place it on the overview grid." }
|
|
1806
|
+
},
|
|
1807
|
+
required: ["name", "source", "measures"]
|
|
1808
|
+
}
|
|
1809
|
+
};
|
|
1810
|
+
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.";
|
|
1811
|
+
function AnalyticsSettingsPage() {
|
|
1812
|
+
const t = useTranslation();
|
|
1813
|
+
const { labels } = useAnalyticsConfig();
|
|
1814
|
+
const catalogue = useAnalyticsCatalogue();
|
|
1815
|
+
const cards = useAnalyticsCards();
|
|
1816
|
+
const prefs = useAnalyticsPrefs();
|
|
1817
|
+
const { createdBy } = useAnalyticsConfig();
|
|
1818
|
+
const [editing, setEditing] = React10__default.useState(null);
|
|
1819
|
+
const [creating, setCreating] = React10__default.useState(false);
|
|
1820
|
+
const [dragId, setDragId] = React10__default.useState(null);
|
|
1821
|
+
const askIntro = React10__default.useMemo(() => ({
|
|
1822
|
+
role: "setup",
|
|
1823
|
+
persona: t("reports.settings.askPersona"),
|
|
1824
|
+
pitch: t("reports.settings.askPitch"),
|
|
1825
|
+
description: t("reports.settings.askDescription"),
|
|
1826
|
+
suggestions: [0, 1, 2].map((i) => ({ label: t(`reports.settings.askSuggestion.${i}`) }))
|
|
1827
|
+
}), [t]);
|
|
1828
|
+
const createActions = React10__default.useMemo(() => [
|
|
1829
|
+
{
|
|
1830
|
+
id: "with-ai",
|
|
1831
|
+
label: t("assistant.askAI"),
|
|
1832
|
+
description: t("reports.settings.newWithAIHint"),
|
|
1833
|
+
icon: "WandSparkles",
|
|
1834
|
+
highlight: true,
|
|
1835
|
+
action: () => askAI({ hint: t("reports.settings.askPlaceholder"), intro: askIntro })
|
|
1836
|
+
},
|
|
1837
|
+
{
|
|
1838
|
+
id: "by-hand",
|
|
1839
|
+
label: t("reports.settings.newByHand"),
|
|
1840
|
+
description: t("reports.settings.newByHandHint"),
|
|
1841
|
+
icon: "SlidersHorizontal",
|
|
1842
|
+
action: () => setCreating(true)
|
|
1843
|
+
}
|
|
1844
|
+
], [t, askIntro]);
|
|
1845
|
+
const sources = React10__default.useMemo(() => analyticsSources(cards), [cards]);
|
|
1846
|
+
const categories = React10__default.useMemo(
|
|
1847
|
+
() => [...new Set(cards.map((c) => c.category))].sort((a, b) => a.localeCompare(b)),
|
|
1848
|
+
[cards]
|
|
1849
|
+
);
|
|
1850
|
+
const pinned = React10__default.useMemo(
|
|
1851
|
+
() => cards.filter((c) => c.dashboard).sort((a, b) => (a.dashboard?.order ?? 0) - (b.dashboard?.order ?? 0)),
|
|
1852
|
+
[cards]
|
|
1853
|
+
);
|
|
1854
|
+
const unpinned = React10__default.useMemo(
|
|
1855
|
+
() => cards.filter((c) => !c.dashboard).sort((a, b) => a.category.localeCompare(b.category) || a.name.localeCompare(b.name)),
|
|
1856
|
+
[cards]
|
|
1857
|
+
);
|
|
1858
|
+
const removed = React10__default.useMemo(
|
|
1859
|
+
() => catalogue.filter((c) => prefs.cards[c.id]?.hidden),
|
|
1860
|
+
[catalogue, prefs]
|
|
1861
|
+
);
|
|
1862
|
+
const [order, setOrder] = React10__default.useState(null);
|
|
1863
|
+
const pinnedIds = order ?? pinned.map((c) => c.id);
|
|
1864
|
+
const pinnedInOrder = pinnedIds.map((id) => pinned.find((c) => c.id === id)).filter((c) => Boolean(c));
|
|
1865
|
+
const move = (id, delta) => {
|
|
1866
|
+
const ids = pinned.map((c) => c.id);
|
|
1867
|
+
const from = ids.indexOf(id);
|
|
1868
|
+
const to = from + delta;
|
|
1869
|
+
if (from < 0 || to < 0 || to >= ids.length) return;
|
|
1870
|
+
ids.splice(to, 0, ids.splice(from, 1)[0]);
|
|
1871
|
+
setOverviewOrder(ids);
|
|
1872
|
+
};
|
|
1873
|
+
const onDragOver = (targetId) => (event) => {
|
|
1874
|
+
event.preventDefault();
|
|
1875
|
+
if (!dragId || dragId === targetId) return;
|
|
1876
|
+
const ids = [...pinnedIds];
|
|
1877
|
+
const from = ids.indexOf(dragId);
|
|
1878
|
+
const to = ids.indexOf(targetId);
|
|
1879
|
+
if (from < 0 || to < 0) return;
|
|
1880
|
+
ids.splice(to, 0, ids.splice(from, 1)[0]);
|
|
1881
|
+
setOrder(ids);
|
|
1882
|
+
};
|
|
1883
|
+
const commitDrag = () => {
|
|
1884
|
+
if (order) setOverviewOrder(order);
|
|
1885
|
+
setOrder(null);
|
|
1886
|
+
setDragId(null);
|
|
1887
|
+
};
|
|
1888
|
+
const editingCard = cards.find((c) => c.id === editing);
|
|
1889
|
+
const isCustom = (id) => prefs.custom.some((r) => r.id === id);
|
|
1890
|
+
const surfaceRef = React10__default.useRef({ sources, categories, cards, prefs, createdBy });
|
|
1891
|
+
surfaceRef.current = { sources, categories, cards, prefs, createdBy };
|
|
1892
|
+
const agentSurface = React10__default.useMemo(() => ({
|
|
1893
|
+
id: "analytics.reports",
|
|
1894
|
+
pluginId: "reports",
|
|
1895
|
+
instructions: AGENT_INSTRUCTIONS,
|
|
1896
|
+
describe: () => {
|
|
1897
|
+
const o = surfaceRef.current;
|
|
1898
|
+
return {
|
|
1899
|
+
title: "Configura\xE7\xE3o de relat\xF3rios",
|
|
1900
|
+
state: {
|
|
1901
|
+
sources: o.sources.map((source) => ({
|
|
1902
|
+
key: source.key,
|
|
1903
|
+
label: source.label,
|
|
1904
|
+
grain: source.base.grain ?? null,
|
|
1905
|
+
group_by: source.dimensions.map((d) => ({ key: d.key, label: d.label })),
|
|
1906
|
+
measures: source.measures.map((m) => ({ key: m.key, label: m.label, format: m.format ?? "number" }))
|
|
1907
|
+
})),
|
|
1908
|
+
categories: o.categories,
|
|
1909
|
+
onOverview: o.cards.filter((c) => c.dashboard).map((c) => c.name),
|
|
1910
|
+
yourReports: o.prefs.custom.map((r) => r.name)
|
|
1911
|
+
}
|
|
1912
|
+
};
|
|
1913
|
+
},
|
|
1914
|
+
tools: [{
|
|
1915
|
+
tool: CREATE_REPORT_TOOL,
|
|
1916
|
+
execute: (args) => {
|
|
1917
|
+
const o = surfaceRef.current;
|
|
1918
|
+
const source = o.sources.find((s) => s.key === args.source) ?? o.sources.find((s) => s.label === args.source);
|
|
1919
|
+
if (!source) {
|
|
1920
|
+
return { ok: false, error: "unknown_source", sources: o.sources.map((s) => s.key) };
|
|
1921
|
+
}
|
|
1922
|
+
const report = buildCustomReport({
|
|
1923
|
+
name: String(args.name ?? "").trim() || "Relat\xF3rio",
|
|
1924
|
+
category: String(args.category ?? o.categories[0] ?? "Geral"),
|
|
1925
|
+
createdBy: o.createdBy,
|
|
1926
|
+
source,
|
|
1927
|
+
dimensions: Array.isArray(args.group_by) ? args.group_by : [],
|
|
1928
|
+
measures: Array.isArray(args.measures) ? args.measures : [],
|
|
1929
|
+
viz: args.viz ?? "table"
|
|
1930
|
+
});
|
|
1931
|
+
if (report.measures.length === 0) {
|
|
1932
|
+
return {
|
|
1933
|
+
ok: false,
|
|
1934
|
+
error: "no_valid_measure",
|
|
1935
|
+
available: source.measures.map((m) => m.key)
|
|
1936
|
+
};
|
|
1937
|
+
}
|
|
1938
|
+
saveCustomReport(report);
|
|
1939
|
+
if (args.pin) setCardPref(report.id, { pinned: true, order: 999 });
|
|
1940
|
+
navigateAnalytics(`${REPORTS_BASE}/${encodeURIComponent(report.id)}`);
|
|
1941
|
+
return {
|
|
1942
|
+
ok: true,
|
|
1943
|
+
id: report.id,
|
|
1944
|
+
name: report.name,
|
|
1945
|
+
group_by: report.dimensions,
|
|
1946
|
+
measures: report.measures,
|
|
1947
|
+
viz: report.viz
|
|
1948
|
+
};
|
|
1949
|
+
}
|
|
1950
|
+
}]
|
|
1951
|
+
}), []);
|
|
1952
|
+
useAgentSurface(agentSurface);
|
|
1953
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
1954
|
+
/* @__PURE__ */ jsx(
|
|
1955
|
+
SubpageHeader,
|
|
1956
|
+
{
|
|
1957
|
+
title: t("reports.settings.title"),
|
|
1958
|
+
parentLabel: labels.analytics,
|
|
1959
|
+
onBack: () => navigateAnalytics(ANALYTICS_BASE)
|
|
1960
|
+
}
|
|
1961
|
+
),
|
|
1962
|
+
/* @__PURE__ */ jsxs(PageHeaderActions, { children: [
|
|
1963
|
+
/* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: () => resetAnalyticsPrefs(), children: [
|
|
1964
|
+
/* @__PURE__ */ jsx(RotateCcw, { className: "mr-1.5 h-3.5 w-3.5" }),
|
|
1965
|
+
t("reports.settings.reset")
|
|
1966
|
+
] }),
|
|
1967
|
+
/* @__PURE__ */ jsx(QuickActionsButton, { label: t("reports.settings.new"), variant: "default", actions: createActions })
|
|
1968
|
+
] }),
|
|
1969
|
+
/* @__PURE__ */ jsx("p", { className: "max-w-2xl text-sm text-muted-foreground", children: t("reports.settings.subtitle") }),
|
|
1970
|
+
/* @__PURE__ */ jsxs(Card, { className: "overflow-hidden", children: [
|
|
1971
|
+
/* @__PURE__ */ jsxs("div", { className: "border-b border-border px-3 py-2", children: [
|
|
1972
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: t("reports.settings.onOverview") }),
|
|
1973
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("reports.settings.onOverviewHint") })
|
|
1974
|
+
] }),
|
|
1975
|
+
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(
|
|
1976
|
+
ReportRow,
|
|
1977
|
+
{
|
|
1978
|
+
card,
|
|
1979
|
+
custom: isCustom(card.id),
|
|
1980
|
+
onEdit: () => setEditing(card.id),
|
|
1981
|
+
drag: {
|
|
1982
|
+
dragging: dragId === card.id,
|
|
1983
|
+
onDragStart: () => setDragId(card.id),
|
|
1984
|
+
onDragOver: onDragOver(card.id),
|
|
1985
|
+
onDragEnd: commitDrag
|
|
1986
|
+
},
|
|
1987
|
+
children: [
|
|
1988
|
+
/* @__PURE__ */ jsxs(
|
|
1989
|
+
Select,
|
|
1990
|
+
{
|
|
1991
|
+
value: String(card.dashboard?.span ?? ""),
|
|
1992
|
+
onValueChange: (value) => setCardPref(card.id, { span: Number(value) }),
|
|
1993
|
+
children: [
|
|
1994
|
+
/* @__PURE__ */ jsx(SelectTrigger, { className: "h-8 w-[150px]", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: t("reports.settings.width") }) }),
|
|
1995
|
+
/* @__PURE__ */ jsx(SelectContent, { children: [1, 2, 3, 4].map((span) => /* @__PURE__ */ jsx(SelectItem, { value: String(span), children: t(`reports.settings.width.${span}`) }, span)) })
|
|
1996
|
+
]
|
|
1997
|
+
}
|
|
1998
|
+
),
|
|
1999
|
+
/* @__PURE__ */ jsx(
|
|
2000
|
+
Button,
|
|
2001
|
+
{
|
|
2002
|
+
variant: "ghost",
|
|
2003
|
+
size: "sm",
|
|
2004
|
+
disabled: index === 0,
|
|
2005
|
+
onClick: () => move(card.id, -1),
|
|
2006
|
+
"aria-label": t("reports.settings.up"),
|
|
2007
|
+
title: t("reports.settings.up"),
|
|
2008
|
+
children: /* @__PURE__ */ jsx(ChevronUp, { className: "h-3.5 w-3.5" })
|
|
2009
|
+
}
|
|
2010
|
+
),
|
|
2011
|
+
/* @__PURE__ */ jsx(
|
|
2012
|
+
Button,
|
|
2013
|
+
{
|
|
2014
|
+
variant: "ghost",
|
|
2015
|
+
size: "sm",
|
|
2016
|
+
disabled: index === pinnedInOrder.length - 1,
|
|
2017
|
+
onClick: () => move(card.id, 1),
|
|
2018
|
+
"aria-label": t("reports.settings.down"),
|
|
2019
|
+
title: t("reports.settings.down"),
|
|
2020
|
+
children: /* @__PURE__ */ jsx(ChevronDown, { className: "h-3.5 w-3.5" })
|
|
2021
|
+
}
|
|
2022
|
+
),
|
|
2023
|
+
/* @__PURE__ */ jsx(
|
|
2024
|
+
Button,
|
|
2025
|
+
{
|
|
2026
|
+
variant: "ghost",
|
|
2027
|
+
size: "sm",
|
|
2028
|
+
onClick: () => setCardPref(card.id, { pinned: false }),
|
|
2029
|
+
"aria-label": t("reports.settings.unpin"),
|
|
2030
|
+
title: t("reports.settings.unpin"),
|
|
2031
|
+
children: /* @__PURE__ */ jsx(EyeOff, { className: "h-3.5 w-3.5" })
|
|
2032
|
+
}
|
|
2033
|
+
)
|
|
2034
|
+
]
|
|
2035
|
+
},
|
|
2036
|
+
card.id
|
|
2037
|
+
)) })
|
|
2038
|
+
] }),
|
|
2039
|
+
/* @__PURE__ */ jsxs(Card, { className: "overflow-hidden", children: [
|
|
2040
|
+
/* @__PURE__ */ jsxs("div", { className: "border-b border-border px-3 py-2", children: [
|
|
2041
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: t("reports.settings.offOverview") }),
|
|
2042
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("reports.settings.offOverviewHint") })
|
|
2043
|
+
] }),
|
|
2044
|
+
/* @__PURE__ */ jsx("ul", { children: unpinned.map((card) => /* @__PURE__ */ jsxs(ReportRow, { card, custom: isCustom(card.id), onEdit: () => setEditing(card.id), children: [
|
|
2045
|
+
/* @__PURE__ */ jsx(
|
|
2046
|
+
Button,
|
|
2047
|
+
{
|
|
2048
|
+
variant: "ghost",
|
|
2049
|
+
size: "sm",
|
|
2050
|
+
onClick: () => setCardPref(card.id, { pinned: true, order: pinned.length }),
|
|
2051
|
+
"aria-label": t("reports.settings.pin"),
|
|
2052
|
+
title: t("reports.settings.pin"),
|
|
2053
|
+
children: /* @__PURE__ */ jsx(Eye, { className: "h-3.5 w-3.5" })
|
|
2054
|
+
}
|
|
2055
|
+
),
|
|
2056
|
+
isCustom(card.id) ? /* @__PURE__ */ jsx(
|
|
2057
|
+
Button,
|
|
2058
|
+
{
|
|
2059
|
+
variant: "ghost",
|
|
2060
|
+
size: "sm",
|
|
2061
|
+
onClick: () => deleteCustomReport(card.id),
|
|
2062
|
+
"aria-label": t("reports.settings.delete"),
|
|
2063
|
+
title: t("reports.settings.delete"),
|
|
2064
|
+
children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
|
|
2065
|
+
}
|
|
2066
|
+
) : /* @__PURE__ */ jsx(
|
|
2067
|
+
Button,
|
|
2068
|
+
{
|
|
2069
|
+
variant: "ghost",
|
|
2070
|
+
size: "sm",
|
|
2071
|
+
onClick: () => setCardPref(card.id, { hidden: true }),
|
|
2072
|
+
"aria-label": t("reports.settings.remove"),
|
|
2073
|
+
title: t("reports.settings.remove"),
|
|
2074
|
+
children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
|
|
2075
|
+
}
|
|
2076
|
+
)
|
|
2077
|
+
] }, card.id)) })
|
|
2078
|
+
] }),
|
|
2079
|
+
removed.length > 0 ? /* @__PURE__ */ jsxs(Card, { className: "overflow-hidden", children: [
|
|
2080
|
+
/* @__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") }) }),
|
|
2081
|
+
/* @__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: [
|
|
2082
|
+
/* @__PURE__ */ jsx("span", { className: "w-4 shrink-0", "aria-hidden": true }),
|
|
2083
|
+
/* @__PURE__ */ jsx(CardIcon, { name: card.icon, className: "h-4 w-4 shrink-0 text-muted-foreground" }),
|
|
2084
|
+
/* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx("span", { className: "truncate text-sm text-muted-foreground", children: card.name }) }),
|
|
2085
|
+
/* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: () => setCardPref(card.id, { hidden: void 0 }), children: [
|
|
2086
|
+
/* @__PURE__ */ jsx(RotateCcw, { className: "mr-1.5 h-3.5 w-3.5" }),
|
|
2087
|
+
t("reports.settings.restore")
|
|
2088
|
+
] })
|
|
2089
|
+
] }, card.id)) })
|
|
2090
|
+
] }) : null,
|
|
2091
|
+
editingCard ? /* @__PURE__ */ jsx(CardEditor, { card: editingCard, onClose: () => setEditing(null) }) : null,
|
|
2092
|
+
creating ? /* @__PURE__ */ jsx(NewReportPanel, { sources, categories, onClose: () => setCreating(false) }) : null
|
|
2093
|
+
] });
|
|
2094
|
+
}
|
|
1200
2095
|
function camelToSnake2(str) {
|
|
1201
2096
|
return str.replace(/[A-Z]/g, (c) => "_" + c.toLowerCase());
|
|
1202
2097
|
}
|
|
@@ -1373,13 +2268,13 @@ function createAnalyticsPlugin(options = {}) {
|
|
|
1373
2268
|
() => createSupabaseAnalyticsProvider({ tenantId: () => getReportsTenantId() }),
|
|
1374
2269
|
() => createMockAnalyticsProvider()
|
|
1375
2270
|
);
|
|
1376
|
-
const page2 = (Body, view) => (props) =>
|
|
2271
|
+
const page2 = (Body, view) => (props) => React10__default.createElement(
|
|
1377
2272
|
AnalyticsSurface,
|
|
1378
2273
|
{ config, provider },
|
|
1379
|
-
|
|
2274
|
+
React10__default.createElement(
|
|
1380
2275
|
AnalyticsModule,
|
|
1381
2276
|
{ view, cardId: props?.cardId },
|
|
1382
|
-
|
|
2277
|
+
React10__default.createElement(Body, props)
|
|
1383
2278
|
)
|
|
1384
2279
|
);
|
|
1385
2280
|
const READ = { feature: "reports", action: "read" };
|
|
@@ -1397,6 +2292,7 @@ function createAnalyticsPlugin(options = {}) {
|
|
|
1397
2292
|
];
|
|
1398
2293
|
return {
|
|
1399
2294
|
id: "reports",
|
|
2295
|
+
defaultAgentRole: "growth",
|
|
1400
2296
|
name: config.labels.analytics,
|
|
1401
2297
|
icon: "BarChart3",
|
|
1402
2298
|
version: "2.0.0",
|
|
@@ -1420,6 +2316,10 @@ function createAnalyticsPlugin(options = {}) {
|
|
|
1420
2316
|
// thing, one of them generic.
|
|
1421
2317
|
{ path: `${REPORTS_BASE}/:cardId`, component: page2(AnalyticsReportViewer, "report"), permission: READ },
|
|
1422
2318
|
{ path: `${ANALYTICS_BASE}/live`, component: page2(LiveViewPage, "live"), permission: READ },
|
|
2319
|
+
// Read permission on purpose: the screen writes a per-user preference,
|
|
2320
|
+
// not tenant data — gating it behind a write scope would mean a viewer
|
|
2321
|
+
// cannot choose which of the reports they can see are on their overview.
|
|
2322
|
+
{ path: SETTINGS_ROUTE, component: page2(AnalyticsSettingsPage, "settings"), permission: READ },
|
|
1423
2323
|
// The old top-level route kept as a redirect target: bookmarks, saved
|
|
1424
2324
|
// links and the five apps still pointing at /reports must not 404.
|
|
1425
2325
|
{ path: "/reports", component: page2(ReportsListPage, "reports"), permission: READ }
|
|
@@ -1449,6 +2349,6 @@ function createReportsPlugin(options) {
|
|
|
1449
2349
|
});
|
|
1450
2350
|
}
|
|
1451
2351
|
|
|
1452
|
-
export { ANALYTICS_BASE, AnalyticsModule, AnalyticsOverview, AnalyticsReportViewer, AnalyticsSurface, REPORTS_BASE, ReportsListPage, aggregateRows, cardColumns, createAnalyticsPlugin, createMockAnalyticsProvider, createMockReportProvider, createReportsPlugin, createSupabaseAnalyticsProvider, createSupabaseReportProvider, getReportsTenantId, markViewed, reportDefToCard, reportDefsToCards, setReportsTenantId, useAnalyticsCards, useAnalyticsConfig, useLastViewed, useOverviewCards };
|
|
2352
|
+
export { ANALYTICS_BASE, AnalyticsModule, AnalyticsOverview, AnalyticsReportViewer, AnalyticsSettingsPage, AnalyticsSurface, REPORTS_BASE, ReportsListPage, SETTINGS_ROUTE, aggregateRows, cardColumns, clearCardPref, createAnalyticsPlugin, createMockAnalyticsProvider, createMockReportProvider, createReportsPlugin, createSupabaseAnalyticsProvider, createSupabaseReportProvider, deleteCustomReport, fieldPalette, getReportsTenantId, markViewed, reportDefToCard, reportDefsToCards, resetAnalyticsPrefs, resolveCards, resolveCustomReport, saveCustomReport, setCardPref, setOverviewOrder, setReportsTenantId, sourceKey, useAnalyticsCards, useAnalyticsCatalogue, useAnalyticsConfig, useAnalyticsPrefs, useLastViewed, useOverviewCards };
|
|
1453
2353
|
//# sourceMappingURL=index.js.map
|
|
1454
2354
|
//# sourceMappingURL=index.js.map
|