@fayz-ai/plugin-reports 0.11.1 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/chunk-4QIHTUXD.js +179 -0
  2. package/dist/chunk-4QIHTUXD.js.map +1 -0
  3. package/dist/components/ExportDropdown.d.ts +7 -1
  4. package/dist/components/ExportDropdown.d.ts.map +1 -1
  5. package/dist/components/ReportPagination.d.ts.map +1 -1
  6. package/dist/{excel-KIJTC7OJ.js → excel-DSORXL4O.js} +6 -5
  7. package/dist/excel-DSORXL4O.js.map +1 -0
  8. package/dist/export/csv.d.ts.map +1 -1
  9. package/dist/export/excel.d.ts.map +1 -1
  10. package/dist/index.d.ts +4 -1
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +990 -68
  13. package/dist/index.js.map +1 -1
  14. package/dist/lib/columns.d.ts +27 -0
  15. package/dist/lib/columns.d.ts.map +1 -1
  16. package/dist/lib/preferences.d.ts +90 -0
  17. package/dist/lib/preferences.d.ts.map +1 -0
  18. package/dist/locales/en.d.ts.map +1 -1
  19. package/dist/locales/pt-BR.d.ts.map +1 -1
  20. package/dist/{pdf-7QQ3U5JQ.js → pdf-DH4CJMV5.js} +3 -3
  21. package/dist/{pdf-7QQ3U5JQ.js.map → pdf-DH4CJMV5.js.map} +1 -1
  22. package/dist/types.d.ts +9 -1
  23. package/dist/types.d.ts.map +1 -1
  24. package/dist/views/AnalyticsAssistant.d.ts.map +1 -1
  25. package/dist/views/AnalyticsModule.d.ts +1 -1
  26. package/dist/views/AnalyticsModule.d.ts.map +1 -1
  27. package/dist/views/AnalyticsReportViewer.d.ts.map +1 -1
  28. package/dist/views/AnalyticsSettingsPage.d.ts +16 -0
  29. package/dist/views/AnalyticsSettingsPage.d.ts.map +1 -0
  30. package/dist/views/AnalyticsSurface.d.ts +22 -3
  31. package/dist/views/AnalyticsSurface.d.ts.map +1 -1
  32. package/package.json +6 -5
  33. package/dist/chunk-4SPBNVQA.js +0 -101
  34. package/dist/chunk-4SPBNVQA.js.map +0 -1
  35. package/dist/excel-KIJTC7OJ.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
- import { reportColumnsToTanstack, formatValuePlain } from './chunk-4SPBNVQA.js';
2
- import * as React6 from 'react';
3
- import React6__default, { useState, useEffect } from 'react';
4
- import { usePluginRuntime, getAnalyticsCards, registerTranslations, createSafeDataProvider, getSupabaseClientOptional, hashRouterAdapter, useTranslation, exportCSV } from '@fayz-ai/core';
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 { expandBagColumns, reportColumnsToTanstack, formatValuePlain } from './chunk-4QIHTUXD.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, useAnalyticsCardRows, 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] = React6.useState(() => read());
629
- React6.useEffect(() => {
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 ConfigContext = React6__default.createContext(null);
951
+ var SETTINGS_ROUTE = `${ANALYTICS_BASE}/settings`;
952
+ var ConfigContext = React10__default.createContext(null);
640
953
  function useAnalyticsConfig() {
641
- const ctx = React6__default.useContext(ConfigContext);
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 useAnalyticsCards() {
958
+ function useAnalyticsCatalogue() {
646
959
  const runtime = usePluginRuntime();
647
960
  const { cards, createdBy } = useAnalyticsConfig();
648
- return React6__default.useMemo(() => {
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 React6__default.useMemo(
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 = React6__default.useCallback((card) => {
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 = React6__default.useMemo(
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 React6__default.useMemo(() => ({
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 = React6__default.useMemo(() => [
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 = React6__default.useMemo(() => {
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(
@@ -832,7 +1174,7 @@ function AnalyticsModule({ view, cardId, children }) {
832
1174
  function AnalyticsOverview() {
833
1175
  const { labels } = useAnalyticsConfig();
834
1176
  const cards = useOverviewCards();
835
- const filters = React6__default.useMemo(() => collectFilters(cards), [cards]);
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
1179
  /* @__PURE__ */ jsx(AnalyticsControls, { filters }),
838
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: [
@@ -852,18 +1194,18 @@ function ReportsListPage() {
852
1194
  const { labels, currency, showHeader } = useAnalyticsConfig();
853
1195
  const cards = useAnalyticsCards();
854
1196
  const lastViewed = useLastViewed();
855
- const [search, setSearch] = React6__default.useState("");
856
- const [category, setCategory] = React6__default.useState();
857
- const [author, setAuthor] = React6__default.useState();
858
- const categoryOptions = React6__default.useMemo(
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(
859
1201
  () => [...new Set(cards.map((c) => c.category))].sort((a, b) => a.localeCompare(b)).map((c) => ({ value: c, label: c })),
860
1202
  [cards]
861
1203
  );
862
- const authorOptions = React6__default.useMemo(
1204
+ const authorOptions = React10__default.useMemo(
863
1205
  () => [...new Set(cards.map((c) => c.createdBy ?? "\u2014"))].sort((a, b) => a.localeCompare(b)).map((a) => ({ value: a, label: a })),
864
1206
  [cards]
865
1207
  );
866
- const rows = React6__default.useMemo(() => {
1208
+ const rows = React10__default.useMemo(() => {
867
1209
  const q = search.trim().toLowerCase();
868
1210
  return cards.filter((card) => {
869
1211
  if (category && card.category !== category) return false;
@@ -872,7 +1214,7 @@ function ReportsListPage() {
872
1214
  return card.name.toLowerCase().includes(q) || card.description?.toLowerCase().includes(q) || card.category.toLowerCase().includes(q);
873
1215
  });
874
1216
  }, [cards, search, category, author]);
875
- const columns = React6__default.useMemo(() => [
1217
+ const columns = React10__default.useMemo(() => [
876
1218
  {
877
1219
  id: "name",
878
1220
  accessorFn: (card) => card.name,
@@ -979,38 +1321,47 @@ function ReportsListPage() {
979
1321
  ] });
980
1322
  }
981
1323
  function exportToCsv(columns, data, filename, currency) {
982
- const visibleCols = columns.filter((c) => c.visible !== false);
1324
+ const { columns: cols, rows } = expandBagColumns(columns, data);
1325
+ const visibleCols = cols.filter((c) => c.visible !== false);
983
1326
  const csvColumns = visibleCols.map((col) => ({
984
1327
  key: col.key,
985
1328
  label: col.label,
986
1329
  format: (value) => formatValuePlain(value, col, currency)
987
1330
  }));
988
- exportCSV(csvColumns, data, filename);
1331
+ exportCSV(csvColumns, rows, filename);
989
1332
  }
990
- function ExportDropdown({ columns, data, filename, currency, title }) {
1333
+ function ExportDropdown({ columns, data, filename, currency, title, fetchAll }) {
991
1334
  const t = useTranslation();
992
1335
  const [excelAvailable, setExcelAvailable] = useState(false);
993
1336
  const [pdfAvailable, setPdfAvailable] = useState(false);
1337
+ const [busy, setBusy] = useState(false);
994
1338
  useEffect(() => {
995
- import('./excel-KIJTC7OJ.js').then((m) => m.isExcelAvailable()).then(setExcelAvailable).catch(() => {
1339
+ import('./excel-DSORXL4O.js').then((m) => m.isExcelAvailable()).then(setExcelAvailable).catch(() => {
996
1340
  });
997
- import('./pdf-7QQ3U5JQ.js').then((m) => m.isPdfAvailable()).then(setPdfAvailable).catch(() => {
1341
+ import('./pdf-DH4CJMV5.js').then((m) => m.isPdfAvailable()).then(setPdfAvailable).catch(() => {
998
1342
  });
999
1343
  }, []);
1000
- const handleCsv = () => exportToCsv(columns, data, filename, currency);
1001
- const handleExcel = async () => {
1002
- const { exportToExcel } = await import('./excel-KIJTC7OJ.js');
1003
- await exportToExcel(columns, data, filename, currency);
1004
- };
1005
- const handlePdf = async () => {
1006
- const { exportToPdf } = await import('./pdf-7QQ3U5JQ.js');
1007
- await exportToPdf(columns, data, filename, { title, currency });
1344
+ const withRows = async (write3) => {
1345
+ if (busy) return;
1346
+ setBusy(true);
1347
+ try {
1348
+ const rows = fetchAll ? await fetchAll().catch(() => data) : data;
1349
+ await write3(rows.length ? rows : data);
1350
+ } finally {
1351
+ setBusy(false);
1352
+ }
1008
1353
  };
1354
+ const handleCsv = () => withRows((rows) => exportToCsv(columns, rows, filename, currency));
1355
+ const handleExcel = () => withRows(async (rows) => {
1356
+ const { exportToExcel } = await import('./excel-DSORXL4O.js');
1357
+ await exportToExcel(columns, rows, filename, currency);
1358
+ });
1359
+ const handlePdf = () => withRows(async (rows) => {
1360
+ const { exportToPdf } = await import('./pdf-DH4CJMV5.js');
1361
+ await exportToPdf(columns, rows, filename, { title, currency });
1362
+ });
1009
1363
  return /* @__PURE__ */ jsxs(Dropdown, { children: [
1010
- /* @__PURE__ */ jsx(DropdownTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", children: [
1011
- /* @__PURE__ */ jsx(Download, { className: "h-4 w-4 mr-1.5" }),
1012
- t("reports.export")
1013
- ] }) }),
1364
+ /* @__PURE__ */ jsx(DropdownTrigger, { asChild: true, children: /* @__PURE__ */ jsx(ToolbarAction, { icon: Download, caret: true, disabled: busy, children: t("reports.export") }) }),
1014
1365
  /* @__PURE__ */ jsxs(DropdownContent, { align: "end", children: [
1015
1366
  /* @__PURE__ */ jsx(DropdownItem, { onClick: handleCsv, children: t("reports.exportCsv") }),
1016
1367
  excelAvailable && /* @__PURE__ */ jsx(DropdownItem, { onClick: handleExcel, children: t("reports.exportExcel") }),
@@ -1019,11 +1370,12 @@ function ExportDropdown({ columns, data, filename, currency, title }) {
1019
1370
  ] });
1020
1371
  }
1021
1372
  function ReportPagination({ page: page2, pageSize, total, onPageChange, onPageSizeChange }) {
1373
+ const t = useTranslation();
1022
1374
  const totalPages = Math.max(1, Math.ceil(total / pageSize));
1023
1375
  const from = (page2 - 1) * pageSize + 1;
1024
1376
  const to = Math.min(page2 * pageSize, total);
1025
1377
  return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-t px-4 py-3", children: [
1026
- /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: total > 0 ? `${from}\u2013${to} of ${total}` : "No results" }),
1378
+ /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: total > 0 ? t("reports.pagination.range", { from, to, total }) : t("reports.pagination.empty") }),
1027
1379
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
1028
1380
  /* @__PURE__ */ jsx(
1029
1381
  "select",
@@ -1031,10 +1383,7 @@ function ReportPagination({ page: page2, pageSize, total, onPageChange, onPageSi
1031
1383
  value: pageSize,
1032
1384
  onChange: (e) => onPageSizeChange(Number(e.target.value)),
1033
1385
  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",
1034
- children: [25, 50, 100].map((size) => /* @__PURE__ */ jsxs("option", { value: size, children: [
1035
- size,
1036
- " / page"
1037
- ] }, size))
1386
+ children: [25, 50, 100].map((size) => /* @__PURE__ */ jsx("option", { value: size, children: t("reports.pagination.perPage", { size }) }, size))
1038
1387
  }
1039
1388
  ),
1040
1389
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
@@ -1071,12 +1420,16 @@ function ViewerBody({ card }) {
1071
1420
  const { labels, currency } = useAnalyticsConfig();
1072
1421
  const { state, setPage, setPageSize } = useAnalyticsContext();
1073
1422
  const { result, loading, error, engineMissing } = useAnalyticsCard(card);
1074
- const columns = React6__default.useMemo(() => cardColumns(card), [card]);
1075
- const tanstackColumns = React6__default.useMemo(
1076
- () => reportColumnsToTanstack(columns, currency.code),
1077
- [columns, currency.code]
1423
+ const columns = React10__default.useMemo(() => cardColumns(card), [card]);
1424
+ const expanded = React10__default.useMemo(
1425
+ () => expandBagColumns(columns, result?.rows ?? []),
1426
+ [columns, result?.rows]
1078
1427
  );
1079
- const rows = result?.rows ?? [];
1428
+ const tanstackColumns = React10__default.useMemo(
1429
+ () => reportColumnsToTanstack(expanded.columns, currency.code),
1430
+ [expanded.columns, currency.code]
1431
+ );
1432
+ const rows = expanded.rows;
1080
1433
  if (card.available === false) {
1081
1434
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center rounded-xl border border-dashed py-16 text-center", children: [
1082
1435
  /* @__PURE__ */ jsx("div", { className: "mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-muted", children: /* @__PURE__ */ jsx(Construction, { className: "h-5 w-5 text-muted-foreground" }) }),
@@ -1129,7 +1482,7 @@ function AnalyticsReportViewer({ cardId }) {
1129
1482
  const { labels } = useAnalyticsConfig();
1130
1483
  const cards = useAnalyticsCards();
1131
1484
  const card = cards.find((c) => c.id === decodeURIComponent(cardId ?? ""));
1132
- React6__default.useEffect(() => {
1485
+ React10__default.useEffect(() => {
1133
1486
  if (card) markViewed(card.id);
1134
1487
  }, [card?.id]);
1135
1488
  if (!card) {
@@ -1164,9 +1517,10 @@ function AnalyticsReportViewer({ cardId }) {
1164
1517
  ] });
1165
1518
  }
1166
1519
  function ViewerActions({ card }) {
1167
- const { currency } = useAnalyticsConfig();
1520
+ const { currency, labels } = useAnalyticsConfig();
1168
1521
  const { result } = useAnalyticsCard(card);
1169
- const columns = React6__default.useMemo(() => cardColumns(card), [card]);
1522
+ const columns = React10__default.useMemo(() => cardColumns(card), [card]);
1523
+ const fetchAll = useAnalyticsCardRows(card);
1170
1524
  if (!result?.rows?.length) return null;
1171
1525
  return /* @__PURE__ */ jsxs(Fragment, { children: [
1172
1526
  /* @__PURE__ */ jsx(
@@ -1174,15 +1528,13 @@ function ViewerActions({ card }) {
1174
1528
  {
1175
1529
  columns,
1176
1530
  data: result.rows,
1531
+ fetchAll,
1177
1532
  filename: card.id,
1178
1533
  currency: currency.code,
1179
1534
  title: card.name
1180
1535
  }
1181
1536
  ),
1182
- /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", className: "h-8", onClick: () => window.print(), children: [
1183
- /* @__PURE__ */ jsx(Printer, { className: "mr-1.5 h-3.5 w-3.5" }),
1184
- "Print"
1185
- ] })
1537
+ /* @__PURE__ */ jsx(ToolbarAction, { icon: Printer, onClick: () => window.print(), children: labels.print })
1186
1538
  ] });
1187
1539
  }
1188
1540
  function LiveViewPage() {
@@ -1193,6 +1545,571 @@ function LiveViewPage() {
1193
1545
  /* @__PURE__ */ jsx("p", { className: "mt-1 max-w-sm text-sm text-muted-foreground", children: labels.liveViewSoon })
1194
1546
  ] });
1195
1547
  }
1548
+ var VIZ_TYPES = ["kpi", "line", "bar", "area", "donut", "breakdown", "table"];
1549
+ function vizOptionsFor(card) {
1550
+ const hasDimension = (card.query.dimensions ?? []).some((d) => !d.hidden);
1551
+ return hasDimension ? VIZ_TYPES : ["kpi", "table"];
1552
+ }
1553
+ function CardIcon({ name, className }) {
1554
+ const Icon = getLucideIcon(name ?? "FileText") ?? getLucideIcon("FileText");
1555
+ return Icon ? /* @__PURE__ */ jsx(Icon, { className }) : null;
1556
+ }
1557
+ function ReportRow({ card, custom, onEdit, children, drag }) {
1558
+ const t = useTranslation();
1559
+ return /* @__PURE__ */ jsxs(
1560
+ "li",
1561
+ {
1562
+ draggable: Boolean(drag),
1563
+ onDragStart: drag?.onDragStart,
1564
+ onDragOver: drag?.onDragOver,
1565
+ onDragEnd: drag?.onDragEnd,
1566
+ className: [
1567
+ "flex items-center gap-2 border-b border-border/60 px-3 py-2 last:border-0",
1568
+ drag ? "cursor-grab active:cursor-grabbing" : "",
1569
+ drag?.dragging ? "opacity-40" : ""
1570
+ ].join(" "),
1571
+ children: [
1572
+ 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 }),
1573
+ /* @__PURE__ */ jsx(CardIcon, { name: card.icon, className: "h-4 w-4 shrink-0 text-muted-foreground" }),
1574
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
1575
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1576
+ /* @__PURE__ */ jsx("span", { className: "truncate text-sm font-medium", children: card.name }),
1577
+ custom ? /* @__PURE__ */ jsx(Badge, { variant: "secondary", children: t("reports.settings.custom") }) : null
1578
+ ] }),
1579
+ /* @__PURE__ */ jsx("p", { className: "truncate text-xs text-muted-foreground", children: card.category })
1580
+ ] }),
1581
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [
1582
+ children,
1583
+ /* @__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" }) })
1584
+ ] })
1585
+ ]
1586
+ }
1587
+ );
1588
+ }
1589
+ function CardEditor({ card, onClose }) {
1590
+ const t = useTranslation();
1591
+ const prefs = useAnalyticsPrefs();
1592
+ const pref = prefs.cards[card.id] ?? {};
1593
+ const isCustom = prefs.custom.some((r) => r.id === card.id);
1594
+ const [name, setName] = React10__default.useState(card.name);
1595
+ const measures = card.query.measures ?? [];
1596
+ const dimensions = (card.query.dimensions ?? []).filter((d) => !d.hidden);
1597
+ const keptMeasures = new Set(pref.measures ?? measures.map((m) => m.key));
1598
+ const keptDimensions = new Set(pref.dimensions ?? dimensions.map((d) => d.key));
1599
+ const toggle = (set, key, field, all) => {
1600
+ const next = new Set(set);
1601
+ if (next.has(key)) next.delete(key);
1602
+ else next.add(key);
1603
+ setCardPref(card.id, { [field]: next.size === all.length ? void 0 : [...next] });
1604
+ };
1605
+ return (
1606
+ // A coluna da direita, não uma gaveta só desta tela: ajustar um relatório é
1607
+ // olhar a lista enquanto se mexe nele, e uma gaveta cobre exatamente a
1608
+ // lista. É a mesma coluna do assistente — RightRailPage cai numa Sheet
1609
+ // sozinha quando a shell não tem coluna nenhuma.
1610
+ /* @__PURE__ */ jsxs(RightRailPage, { title: card.name, open: true, onClose, width: "max-w-md", children: [
1611
+ /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto p-4", children: /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
1612
+ card.description ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: card.description }) : null,
1613
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
1614
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("reports.settings.name") }),
1615
+ /* @__PURE__ */ jsx(
1616
+ Input,
1617
+ {
1618
+ value: name,
1619
+ onChange: (e) => setName(e.target.value),
1620
+ onBlur: () => setCardPref(card.id, { name: name.trim() && name !== card.name ? name.trim() : void 0 })
1621
+ }
1622
+ )
1623
+ ] }),
1624
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
1625
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("reports.settings.viz") }),
1626
+ /* @__PURE__ */ jsxs(
1627
+ Select,
1628
+ {
1629
+ value: card.viz.type,
1630
+ onValueChange: (value) => setCardPref(card.id, { viz: value }),
1631
+ children: [
1632
+ /* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
1633
+ /* @__PURE__ */ jsx(SelectContent, { children: vizOptionsFor(card).map((viz) => /* @__PURE__ */ jsx(SelectItem, { value: viz, children: t(`reports.viz.${viz}`) }, viz)) })
1634
+ ]
1635
+ }
1636
+ )
1637
+ ] }),
1638
+ dimensions.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1639
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground", children: t("reports.settings.dimensions") }),
1640
+ dimensions.map((d) => /* @__PURE__ */ jsxs("label", { className: "flex cursor-pointer items-center gap-2 text-sm", children: [
1641
+ /* @__PURE__ */ jsx(
1642
+ Checkbox,
1643
+ {
1644
+ checked: keptDimensions.has(d.key),
1645
+ onChange: () => toggle(keptDimensions, d.key, "dimensions", dimensions.map((x) => x.key))
1646
+ }
1647
+ ),
1648
+ d.label
1649
+ ] }, d.key))
1650
+ ] }) : null,
1651
+ measures.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1652
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground", children: t("reports.settings.measures") }),
1653
+ measures.map((m) => /* @__PURE__ */ jsxs("label", { className: "flex cursor-pointer items-center gap-2 text-sm", children: [
1654
+ /* @__PURE__ */ jsx(
1655
+ Checkbox,
1656
+ {
1657
+ checked: keptMeasures.has(m.key),
1658
+ onChange: () => toggle(keptMeasures, m.key, "measures", measures.map((x) => x.key))
1659
+ }
1660
+ ),
1661
+ m.label
1662
+ ] }, m.key))
1663
+ ] }) : null
1664
+ ] }) }),
1665
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-t border-border p-3", children: [
1666
+ isCustom ? /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: () => {
1667
+ deleteCustomReport(card.id);
1668
+ onClose();
1669
+ }, children: [
1670
+ /* @__PURE__ */ jsx(Trash2, { className: "mr-1.5 h-3.5 w-3.5" }),
1671
+ t("reports.settings.delete")
1672
+ ] }) : /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: () => {
1673
+ clearCardPref(card.id);
1674
+ onClose();
1675
+ }, children: [
1676
+ /* @__PURE__ */ jsx(RotateCcw, { className: "mr-1.5 h-3.5 w-3.5" }),
1677
+ t("reports.settings.resetCard")
1678
+ ] }),
1679
+ /* @__PURE__ */ jsx(Button, { size: "sm", onClick: onClose, children: t("reports.settings.done") })
1680
+ ] })
1681
+ ] })
1682
+ );
1683
+ }
1684
+ function slugify(value) {
1685
+ return value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "report";
1686
+ }
1687
+ function buildCustomReport(input) {
1688
+ const dimensionKeys = new Set(input.source.dimensions.map((d) => d.key));
1689
+ const measureKeys = new Set(input.source.measures.map((m) => m.key));
1690
+ return {
1691
+ id: `custom.${slugify(input.name)}-${Date.now().toString(36)}`,
1692
+ name: input.name.trim(),
1693
+ category: input.category.trim() || "Geral",
1694
+ icon: "FileText",
1695
+ createdBy: input.createdBy,
1696
+ baseId: input.source.base.id,
1697
+ // Filtered, not trusted: a field the source does not have would resolve to
1698
+ // nothing and the report would open empty with no explanation.
1699
+ dimensions: input.dimensions.filter((k) => dimensionKeys.has(k)),
1700
+ measures: input.measures.filter((k) => measureKeys.has(k)),
1701
+ viz: input.viz,
1702
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1703
+ };
1704
+ }
1705
+ function NewReportPanel({
1706
+ sources,
1707
+ categories,
1708
+ onClose
1709
+ }) {
1710
+ const t = useTranslation();
1711
+ const { createdBy } = useAnalyticsConfig();
1712
+ const [name, setName] = React10__default.useState("");
1713
+ const [category, setCategory] = React10__default.useState(categories[0] ?? "Geral");
1714
+ const [sourceKeyState, setSourceKey] = React10__default.useState(sources[0]?.key ?? "");
1715
+ const [dimensions, setDimensions] = React10__default.useState([]);
1716
+ const [measures, setMeasures] = React10__default.useState([]);
1717
+ const [viz, setViz] = React10__default.useState("table");
1718
+ const source = sources.find((s) => s.key === sourceKeyState);
1719
+ React10__default.useEffect(() => {
1720
+ setDimensions([]);
1721
+ setMeasures([]);
1722
+ }, [sourceKeyState]);
1723
+ const toggle = (list, set, key) => {
1724
+ set(list.includes(key) ? list.filter((k) => k !== key) : [...list, key]);
1725
+ };
1726
+ const error = !name.trim() ? t("reports.settings.needName") : measures.length === 0 ? t("reports.settings.needMeasure") : null;
1727
+ const submit = () => {
1728
+ if (error || !source) return;
1729
+ saveCustomReport(buildCustomReport({
1730
+ name,
1731
+ category,
1732
+ createdBy,
1733
+ source,
1734
+ dimensions,
1735
+ measures,
1736
+ viz
1737
+ }));
1738
+ onClose();
1739
+ };
1740
+ return /* @__PURE__ */ jsxs(RightRailPage, { title: t("reports.settings.new"), open: true, onClose, width: "max-w-md", children: [
1741
+ /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto p-4", children: /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
1742
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
1743
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("reports.settings.name") }),
1744
+ /* @__PURE__ */ jsx(Input, { value: name, onChange: (e) => setName(e.target.value) })
1745
+ ] }),
1746
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
1747
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("reports.settings.category") }),
1748
+ /* @__PURE__ */ jsx(Input, { value: category, onChange: (e) => setCategory(e.target.value), list: "fayz-analytics-categories" }),
1749
+ /* @__PURE__ */ jsx("datalist", { id: "fayz-analytics-categories", children: categories.map((c) => /* @__PURE__ */ jsx("option", { value: c }, c)) })
1750
+ ] }),
1751
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
1752
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("reports.settings.source") }),
1753
+ /* @__PURE__ */ jsxs(Select, { value: sourceKeyState, onValueChange: setSourceKey, children: [
1754
+ /* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
1755
+ /* @__PURE__ */ jsx(SelectContent, { children: sources.map((s) => /* @__PURE__ */ jsx(SelectItem, { value: s.key, children: s.label }, s.key)) })
1756
+ ] }),
1757
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("reports.settings.sourceHint") })
1758
+ ] }),
1759
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1760
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground", children: t("reports.settings.dimensions") }),
1761
+ (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: [
1762
+ /* @__PURE__ */ jsx(Checkbox, { checked: dimensions.includes(d.key), onChange: () => toggle(dimensions, setDimensions, d.key) }),
1763
+ d.label
1764
+ ] }, d.key))
1765
+ ] }),
1766
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1767
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground", children: t("reports.settings.measures") }),
1768
+ (source?.measures ?? []).map((m) => /* @__PURE__ */ jsxs("label", { className: "flex cursor-pointer items-center gap-2 text-sm", children: [
1769
+ /* @__PURE__ */ jsx(Checkbox, { checked: measures.includes(m.key), onChange: () => toggle(measures, setMeasures, m.key) }),
1770
+ m.label
1771
+ ] }, m.key))
1772
+ ] }),
1773
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
1774
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("reports.settings.viz") }),
1775
+ /* @__PURE__ */ jsxs(Select, { value: viz, onValueChange: (value) => setViz(value), children: [
1776
+ /* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
1777
+ /* @__PURE__ */ jsx(SelectContent, { children: (dimensions.length > 0 ? VIZ_TYPES : ["kpi", "table"]).map((v) => /* @__PURE__ */ jsx(SelectItem, { value: v, children: t(`reports.viz.${v}`) }, v)) })
1778
+ ] })
1779
+ ] })
1780
+ ] }) }),
1781
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 border-t border-border p-3", children: [
1782
+ /* @__PURE__ */ jsx("span", { className: "truncate text-xs text-destructive", children: error }),
1783
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [
1784
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: onClose, children: t("reports.settings.cancel") }),
1785
+ /* @__PURE__ */ jsx(Button, { size: "sm", onClick: submit, disabled: Boolean(error), children: t("reports.settings.save") })
1786
+ ] })
1787
+ ] })
1788
+ ] });
1789
+ }
1790
+ var CREATE_REPORT_TOOL = {
1791
+ id: "reports.createAnalyticsReport",
1792
+ name: "createAnalyticsReport",
1793
+ 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.",
1794
+ icon: "FileText",
1795
+ mode: "persist",
1796
+ execution: { plane: "client" },
1797
+ category: "Analytics",
1798
+ suggestions: [
1799
+ { label: "Um relat\xF3rio de receita por mesa" },
1800
+ { label: "Quanto cada pra\xE7a produz por turno" }
1801
+ ],
1802
+ parameters: {
1803
+ type: "object",
1804
+ properties: {
1805
+ name: { type: "string", description: "Report name, in the user's language." },
1806
+ source: { type: "string", description: "A `key` from the surface state's `sources`." },
1807
+ group_by: {
1808
+ type: "array",
1809
+ description: "Dimension keys from that source. Empty means a single headline number.",
1810
+ items: { type: "string" }
1811
+ },
1812
+ measures: {
1813
+ type: "array",
1814
+ description: "Measure keys from that source. At least one.",
1815
+ items: { type: "string" }
1816
+ },
1817
+ viz: {
1818
+ type: "string",
1819
+ enum: ["kpi", "line", "bar", "area", "donut", "breakdown", "table"],
1820
+ description: "How to draw it. Defaults to table."
1821
+ },
1822
+ category: { type: "string", description: "Group it sits under in the rail. Reuse an existing one when it fits." },
1823
+ pin: { type: "boolean", description: "Also place it on the overview grid." }
1824
+ },
1825
+ required: ["name", "source", "measures"]
1826
+ }
1827
+ };
1828
+ 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.";
1829
+ function AnalyticsSettingsPage() {
1830
+ const t = useTranslation();
1831
+ const { labels } = useAnalyticsConfig();
1832
+ const catalogue = useAnalyticsCatalogue();
1833
+ const cards = useAnalyticsCards();
1834
+ const prefs = useAnalyticsPrefs();
1835
+ const { createdBy } = useAnalyticsConfig();
1836
+ const [editing, setEditing] = React10__default.useState(null);
1837
+ const [creating, setCreating] = React10__default.useState(false);
1838
+ const [dragId, setDragId] = React10__default.useState(null);
1839
+ const askIntro = React10__default.useMemo(() => ({
1840
+ role: "setup",
1841
+ persona: t("reports.settings.askPersona"),
1842
+ pitch: t("reports.settings.askPitch"),
1843
+ description: t("reports.settings.askDescription"),
1844
+ suggestions: [0, 1, 2].map((i) => ({ label: t(`reports.settings.askSuggestion.${i}`) }))
1845
+ }), [t]);
1846
+ const createActions = React10__default.useMemo(() => [
1847
+ {
1848
+ id: "with-ai",
1849
+ label: t("assistant.askAI"),
1850
+ description: t("reports.settings.newWithAIHint"),
1851
+ icon: "WandSparkles",
1852
+ highlight: true,
1853
+ action: () => askAI({ hint: t("reports.settings.askPlaceholder"), intro: askIntro })
1854
+ },
1855
+ {
1856
+ id: "by-hand",
1857
+ label: t("reports.settings.newByHand"),
1858
+ description: t("reports.settings.newByHandHint"),
1859
+ icon: "SlidersHorizontal",
1860
+ action: () => setCreating(true)
1861
+ }
1862
+ ], [t, askIntro]);
1863
+ const sources = React10__default.useMemo(() => analyticsSources(cards), [cards]);
1864
+ const categories = React10__default.useMemo(
1865
+ () => [...new Set(cards.map((c) => c.category))].sort((a, b) => a.localeCompare(b)),
1866
+ [cards]
1867
+ );
1868
+ const pinned = React10__default.useMemo(
1869
+ () => cards.filter((c) => c.dashboard).sort((a, b) => (a.dashboard?.order ?? 0) - (b.dashboard?.order ?? 0)),
1870
+ [cards]
1871
+ );
1872
+ const unpinned = React10__default.useMemo(
1873
+ () => cards.filter((c) => !c.dashboard).sort((a, b) => a.category.localeCompare(b.category) || a.name.localeCompare(b.name)),
1874
+ [cards]
1875
+ );
1876
+ const removed = React10__default.useMemo(
1877
+ () => catalogue.filter((c) => prefs.cards[c.id]?.hidden),
1878
+ [catalogue, prefs]
1879
+ );
1880
+ const [order, setOrder] = React10__default.useState(null);
1881
+ const pinnedIds = order ?? pinned.map((c) => c.id);
1882
+ const pinnedInOrder = pinnedIds.map((id) => pinned.find((c) => c.id === id)).filter((c) => Boolean(c));
1883
+ const move = (id, delta) => {
1884
+ const ids = pinned.map((c) => c.id);
1885
+ const from = ids.indexOf(id);
1886
+ const to = from + delta;
1887
+ if (from < 0 || to < 0 || to >= ids.length) return;
1888
+ ids.splice(to, 0, ids.splice(from, 1)[0]);
1889
+ setOverviewOrder(ids);
1890
+ };
1891
+ const onDragOver = (targetId) => (event) => {
1892
+ event.preventDefault();
1893
+ if (!dragId || dragId === targetId) return;
1894
+ const ids = [...pinnedIds];
1895
+ const from = ids.indexOf(dragId);
1896
+ const to = ids.indexOf(targetId);
1897
+ if (from < 0 || to < 0) return;
1898
+ ids.splice(to, 0, ids.splice(from, 1)[0]);
1899
+ setOrder(ids);
1900
+ };
1901
+ const commitDrag = () => {
1902
+ if (order) setOverviewOrder(order);
1903
+ setOrder(null);
1904
+ setDragId(null);
1905
+ };
1906
+ const editingCard = cards.find((c) => c.id === editing);
1907
+ const isCustom = (id) => prefs.custom.some((r) => r.id === id);
1908
+ const surfaceRef = React10__default.useRef({ sources, categories, cards, prefs, createdBy });
1909
+ surfaceRef.current = { sources, categories, cards, prefs, createdBy };
1910
+ const agentSurface = React10__default.useMemo(() => ({
1911
+ id: "analytics.reports",
1912
+ pluginId: "reports",
1913
+ instructions: AGENT_INSTRUCTIONS,
1914
+ describe: () => {
1915
+ const o = surfaceRef.current;
1916
+ return {
1917
+ title: "Configura\xE7\xE3o de relat\xF3rios",
1918
+ state: {
1919
+ sources: o.sources.map((source) => ({
1920
+ key: source.key,
1921
+ label: source.label,
1922
+ grain: source.base.grain ?? null,
1923
+ group_by: source.dimensions.map((d) => ({ key: d.key, label: d.label })),
1924
+ measures: source.measures.map((m) => ({ key: m.key, label: m.label, format: m.format ?? "number" }))
1925
+ })),
1926
+ categories: o.categories,
1927
+ onOverview: o.cards.filter((c) => c.dashboard).map((c) => c.name),
1928
+ yourReports: o.prefs.custom.map((r) => r.name)
1929
+ }
1930
+ };
1931
+ },
1932
+ tools: [{
1933
+ tool: CREATE_REPORT_TOOL,
1934
+ execute: (args) => {
1935
+ const o = surfaceRef.current;
1936
+ const source = o.sources.find((s) => s.key === args.source) ?? o.sources.find((s) => s.label === args.source);
1937
+ if (!source) {
1938
+ return { ok: false, error: "unknown_source", sources: o.sources.map((s) => s.key) };
1939
+ }
1940
+ const report = buildCustomReport({
1941
+ name: String(args.name ?? "").trim() || "Relat\xF3rio",
1942
+ category: String(args.category ?? o.categories[0] ?? "Geral"),
1943
+ createdBy: o.createdBy,
1944
+ source,
1945
+ dimensions: Array.isArray(args.group_by) ? args.group_by : [],
1946
+ measures: Array.isArray(args.measures) ? args.measures : [],
1947
+ viz: args.viz ?? "table"
1948
+ });
1949
+ if (report.measures.length === 0) {
1950
+ return {
1951
+ ok: false,
1952
+ error: "no_valid_measure",
1953
+ available: source.measures.map((m) => m.key)
1954
+ };
1955
+ }
1956
+ saveCustomReport(report);
1957
+ if (args.pin) setCardPref(report.id, { pinned: true, order: 999 });
1958
+ navigateAnalytics(`${REPORTS_BASE}/${encodeURIComponent(report.id)}`);
1959
+ return {
1960
+ ok: true,
1961
+ id: report.id,
1962
+ name: report.name,
1963
+ group_by: report.dimensions,
1964
+ measures: report.measures,
1965
+ viz: report.viz
1966
+ };
1967
+ }
1968
+ }]
1969
+ }), []);
1970
+ useAgentSurface(agentSurface);
1971
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
1972
+ /* @__PURE__ */ jsx(
1973
+ SubpageHeader,
1974
+ {
1975
+ title: t("reports.settings.title"),
1976
+ parentLabel: labels.analytics,
1977
+ onBack: () => navigateAnalytics(ANALYTICS_BASE)
1978
+ }
1979
+ ),
1980
+ /* @__PURE__ */ jsxs(PageHeaderActions, { children: [
1981
+ /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: () => resetAnalyticsPrefs(), children: [
1982
+ /* @__PURE__ */ jsx(RotateCcw, { className: "mr-1.5 h-3.5 w-3.5" }),
1983
+ t("reports.settings.reset")
1984
+ ] }),
1985
+ /* @__PURE__ */ jsx(QuickActionsButton, { label: t("reports.settings.new"), variant: "default", actions: createActions })
1986
+ ] }),
1987
+ /* @__PURE__ */ jsx("p", { className: "max-w-2xl text-sm text-muted-foreground", children: t("reports.settings.subtitle") }),
1988
+ /* @__PURE__ */ jsxs(Card, { className: "overflow-hidden", children: [
1989
+ /* @__PURE__ */ jsxs("div", { className: "border-b border-border px-3 py-2", children: [
1990
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: t("reports.settings.onOverview") }),
1991
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("reports.settings.onOverviewHint") })
1992
+ ] }),
1993
+ 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(
1994
+ ReportRow,
1995
+ {
1996
+ card,
1997
+ custom: isCustom(card.id),
1998
+ onEdit: () => setEditing(card.id),
1999
+ drag: {
2000
+ dragging: dragId === card.id,
2001
+ onDragStart: () => setDragId(card.id),
2002
+ onDragOver: onDragOver(card.id),
2003
+ onDragEnd: commitDrag
2004
+ },
2005
+ children: [
2006
+ /* @__PURE__ */ jsxs(
2007
+ Select,
2008
+ {
2009
+ value: String(card.dashboard?.span ?? ""),
2010
+ onValueChange: (value) => setCardPref(card.id, { span: Number(value) }),
2011
+ children: [
2012
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "h-8 w-[150px]", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: t("reports.settings.width") }) }),
2013
+ /* @__PURE__ */ jsx(SelectContent, { children: [1, 2, 3, 4].map((span) => /* @__PURE__ */ jsx(SelectItem, { value: String(span), children: t(`reports.settings.width.${span}`) }, span)) })
2014
+ ]
2015
+ }
2016
+ ),
2017
+ /* @__PURE__ */ jsx(
2018
+ Button,
2019
+ {
2020
+ variant: "ghost",
2021
+ size: "sm",
2022
+ disabled: index === 0,
2023
+ onClick: () => move(card.id, -1),
2024
+ "aria-label": t("reports.settings.up"),
2025
+ title: t("reports.settings.up"),
2026
+ children: /* @__PURE__ */ jsx(ChevronUp, { className: "h-3.5 w-3.5" })
2027
+ }
2028
+ ),
2029
+ /* @__PURE__ */ jsx(
2030
+ Button,
2031
+ {
2032
+ variant: "ghost",
2033
+ size: "sm",
2034
+ disabled: index === pinnedInOrder.length - 1,
2035
+ onClick: () => move(card.id, 1),
2036
+ "aria-label": t("reports.settings.down"),
2037
+ title: t("reports.settings.down"),
2038
+ children: /* @__PURE__ */ jsx(ChevronDown, { className: "h-3.5 w-3.5" })
2039
+ }
2040
+ ),
2041
+ /* @__PURE__ */ jsx(
2042
+ Button,
2043
+ {
2044
+ variant: "ghost",
2045
+ size: "sm",
2046
+ onClick: () => setCardPref(card.id, { pinned: false }),
2047
+ "aria-label": t("reports.settings.unpin"),
2048
+ title: t("reports.settings.unpin"),
2049
+ children: /* @__PURE__ */ jsx(EyeOff, { className: "h-3.5 w-3.5" })
2050
+ }
2051
+ )
2052
+ ]
2053
+ },
2054
+ card.id
2055
+ )) })
2056
+ ] }),
2057
+ /* @__PURE__ */ jsxs(Card, { className: "overflow-hidden", children: [
2058
+ /* @__PURE__ */ jsxs("div", { className: "border-b border-border px-3 py-2", children: [
2059
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: t("reports.settings.offOverview") }),
2060
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("reports.settings.offOverviewHint") })
2061
+ ] }),
2062
+ /* @__PURE__ */ jsx("ul", { children: unpinned.map((card) => /* @__PURE__ */ jsxs(ReportRow, { card, custom: isCustom(card.id), onEdit: () => setEditing(card.id), children: [
2063
+ /* @__PURE__ */ jsx(
2064
+ Button,
2065
+ {
2066
+ variant: "ghost",
2067
+ size: "sm",
2068
+ onClick: () => setCardPref(card.id, { pinned: true, order: pinned.length }),
2069
+ "aria-label": t("reports.settings.pin"),
2070
+ title: t("reports.settings.pin"),
2071
+ children: /* @__PURE__ */ jsx(Eye, { className: "h-3.5 w-3.5" })
2072
+ }
2073
+ ),
2074
+ isCustom(card.id) ? /* @__PURE__ */ jsx(
2075
+ Button,
2076
+ {
2077
+ variant: "ghost",
2078
+ size: "sm",
2079
+ onClick: () => deleteCustomReport(card.id),
2080
+ "aria-label": t("reports.settings.delete"),
2081
+ title: t("reports.settings.delete"),
2082
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
2083
+ }
2084
+ ) : /* @__PURE__ */ jsx(
2085
+ Button,
2086
+ {
2087
+ variant: "ghost",
2088
+ size: "sm",
2089
+ onClick: () => setCardPref(card.id, { hidden: true }),
2090
+ "aria-label": t("reports.settings.remove"),
2091
+ title: t("reports.settings.remove"),
2092
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
2093
+ }
2094
+ )
2095
+ ] }, card.id)) })
2096
+ ] }),
2097
+ removed.length > 0 ? /* @__PURE__ */ jsxs(Card, { className: "overflow-hidden", children: [
2098
+ /* @__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") }) }),
2099
+ /* @__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: [
2100
+ /* @__PURE__ */ jsx("span", { className: "w-4 shrink-0", "aria-hidden": true }),
2101
+ /* @__PURE__ */ jsx(CardIcon, { name: card.icon, className: "h-4 w-4 shrink-0 text-muted-foreground" }),
2102
+ /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx("span", { className: "truncate text-sm text-muted-foreground", children: card.name }) }),
2103
+ /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: () => setCardPref(card.id, { hidden: void 0 }), children: [
2104
+ /* @__PURE__ */ jsx(RotateCcw, { className: "mr-1.5 h-3.5 w-3.5" }),
2105
+ t("reports.settings.restore")
2106
+ ] })
2107
+ ] }, card.id)) })
2108
+ ] }) : null,
2109
+ editingCard ? /* @__PURE__ */ jsx(CardEditor, { card: editingCard, onClose: () => setEditing(null) }) : null,
2110
+ creating ? /* @__PURE__ */ jsx(NewReportPanel, { sources, categories, onClose: () => setCreating(false) }) : null
2111
+ ] });
2112
+ }
1196
2113
  function camelToSnake2(str) {
1197
2114
  return str.replace(/[A-Z]/g, (c) => "_" + c.toLowerCase());
1198
2115
  }
@@ -1369,13 +2286,13 @@ function createAnalyticsPlugin(options = {}) {
1369
2286
  () => createSupabaseAnalyticsProvider({ tenantId: () => getReportsTenantId() }),
1370
2287
  () => createMockAnalyticsProvider()
1371
2288
  );
1372
- const page2 = (Body, view) => (props) => React6__default.createElement(
2289
+ const page2 = (Body, view) => (props) => React10__default.createElement(
1373
2290
  AnalyticsSurface,
1374
2291
  { config, provider },
1375
- React6__default.createElement(
2292
+ React10__default.createElement(
1376
2293
  AnalyticsModule,
1377
2294
  { view, cardId: props?.cardId },
1378
- React6__default.createElement(Body, props)
2295
+ React10__default.createElement(Body, props)
1379
2296
  )
1380
2297
  );
1381
2298
  const READ = { feature: "reports", action: "read" };
@@ -1393,6 +2310,7 @@ function createAnalyticsPlugin(options = {}) {
1393
2310
  ];
1394
2311
  return {
1395
2312
  id: "reports",
2313
+ defaultAgentRole: "growth",
1396
2314
  name: config.labels.analytics,
1397
2315
  icon: "BarChart3",
1398
2316
  version: "2.0.0",
@@ -1416,6 +2334,10 @@ function createAnalyticsPlugin(options = {}) {
1416
2334
  // thing, one of them generic.
1417
2335
  { path: `${REPORTS_BASE}/:cardId`, component: page2(AnalyticsReportViewer, "report"), permission: READ },
1418
2336
  { path: `${ANALYTICS_BASE}/live`, component: page2(LiveViewPage, "live"), permission: READ },
2337
+ // Read permission on purpose: the screen writes a per-user preference,
2338
+ // not tenant data — gating it behind a write scope would mean a viewer
2339
+ // cannot choose which of the reports they can see are on their overview.
2340
+ { path: SETTINGS_ROUTE, component: page2(AnalyticsSettingsPage, "settings"), permission: READ },
1419
2341
  // The old top-level route kept as a redirect target: bookmarks, saved
1420
2342
  // links and the five apps still pointing at /reports must not 404.
1421
2343
  { path: "/reports", component: page2(ReportsListPage, "reports"), permission: READ }
@@ -1445,6 +2367,6 @@ function createReportsPlugin(options) {
1445
2367
  });
1446
2368
  }
1447
2369
 
1448
- 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 };
2370
+ 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 };
1449
2371
  //# sourceMappingURL=index.js.map
1450
2372
  //# sourceMappingURL=index.js.map