@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/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 { 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] = 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(
@@ -830,15 +1172,11 @@ function AnalyticsModule({ view, cardId, children }) {
830
1172
  );
831
1173
  }
832
1174
  function AnalyticsOverview() {
833
- const { labels, showHeader } = useAnalyticsConfig();
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
- showHeader ? /* @__PURE__ */ jsxs("div", { children: [
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] = React6__default.useState("");
860
- const [category, setCategory] = React6__default.useState();
861
- const [author, setAuthor] = React6__default.useState();
862
- 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(
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 = React6__default.useMemo(
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 = React6__default.useMemo(() => {
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 = React6__default.useMemo(() => [
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-KIJTC7OJ.js').then((m) => m.isExcelAvailable()).then(setExcelAvailable).catch(() => {
1337
+ import('./excel-LDM5H5IW.js').then((m) => m.isExcelAvailable()).then(setExcelAvailable).catch(() => {
1000
1338
  });
1001
- import('./pdf-7QQ3U5JQ.js').then((m) => m.isPdfAvailable()).then(setPdfAvailable).catch(() => {
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-KIJTC7OJ.js');
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-7QQ3U5JQ.js');
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__ */ jsxs(Button, { variant: "outline", size: "sm", children: [
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 ? `${from}\u2013${to} of ${total}` : "No results" }),
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__ */ jsxs("option", { value: size, children: [
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 = React6__default.useMemo(() => cardColumns(card), [card]);
1079
- const tanstackColumns = React6__default.useMemo(
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
- React6__default.useEffect(() => {
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 = React6__default.useMemo(() => cardColumns(card), [card]);
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__ */ jsxs(Button, { variant: "outline", size: "sm", className: "h-8", onClick: () => window.print(), children: [
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) => React6__default.createElement(
2271
+ const page2 = (Body, view) => (props) => React10__default.createElement(
1377
2272
  AnalyticsSurface,
1378
2273
  { config, provider },
1379
- React6__default.createElement(
2274
+ React10__default.createElement(
1380
2275
  AnalyticsModule,
1381
2276
  { view, cardId: props?.cardId },
1382
- React6__default.createElement(Body, props)
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