@fayz-ai/plugin-reports 0.12.0 → 0.12.2

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.
@@ -33,8 +33,59 @@ function formatDuration(value) {
33
33
  const m = minutes % 60;
34
34
  return m ? `${h}h ${m}min` : `${h}h`;
35
35
  }
36
+ function plainValue(value) {
37
+ if (value == null || value === "") return "";
38
+ if (Array.isArray(value)) return value.map(plainValue).filter(Boolean).join(", ");
39
+ if (typeof value === "boolean") return value ? "Sim" : "N\xE3o";
40
+ if (typeof value === "object") {
41
+ return Object.entries(value).filter(([, v]) => v !== null && v !== void 0 && v !== "").map(([k, v]) => `${humanizeKey(k)}: ${plainValue(v)}`).join(" \xB7 ");
42
+ }
43
+ return String(value);
44
+ }
45
+ function humanizeKey(key) {
46
+ const spaced = key.replace(/[_-]+/g, " ").replace(/([a-z\d])([A-Z])/g, "$1 $2");
47
+ return spaced.charAt(0).toUpperCase() + spaced.slice(1);
48
+ }
49
+ function expandBagColumns(columns, data) {
50
+ const bags = columns.filter((c) => c.expand);
51
+ if (!bags.length) return { columns, rows: data };
52
+ const keysByBag = /* @__PURE__ */ new Map();
53
+ for (const bag of bags) {
54
+ const keys = [];
55
+ for (const row of data) {
56
+ const value = row?.[bag.key];
57
+ if (!value || typeof value !== "object" || Array.isArray(value)) continue;
58
+ for (const key of Object.keys(value)) if (!keys.includes(key)) keys.push(key);
59
+ }
60
+ keysByBag.set(bag.key, keys);
61
+ }
62
+ const expanded = columns.flatMap((col) => {
63
+ if (!col.expand) return [col];
64
+ return (keysByBag.get(col.key) ?? []).map((key) => ({
65
+ key: `${col.key}.${key}`,
66
+ label: humanizeKey(key),
67
+ type: "text",
68
+ // The key exists only on the flattened row, never on the read model, so
69
+ // a server-side ORDER BY on it would 42703. Client sort only.
70
+ sortable: false
71
+ }));
72
+ });
73
+ const rows = data.map((row) => {
74
+ const next = { ...row };
75
+ for (const [bagKey, keys] of keysByBag) {
76
+ const bag = row?.[bagKey];
77
+ for (const key of keys) {
78
+ next[`${bagKey}.${key}`] = bag && typeof bag === "object" ? plainValue(bag[key]) : "";
79
+ }
80
+ }
81
+ return next;
82
+ });
83
+ return { columns: expanded, rows };
84
+ }
36
85
  function formatValuePlain(value, col, currency) {
37
86
  if (value == null || value === "") return "";
87
+ if (col.valueMap) return col.valueMap[String(value)] ?? String(value);
88
+ if (typeof value === "object") return plainValue(value);
38
89
  switch (col.type) {
39
90
  case "currency":
40
91
  return formatCurrency(value, col.currency ?? currency);
@@ -69,9 +120,13 @@ function reportColumnsToTanstack(columns, currency) {
69
120
  if (col.renderCell) {
70
121
  return col.renderCell(value, row);
71
122
  }
72
- if (value == null || value === "") {
123
+ if (value == null || value === "" || typeof value === "object" && !plainValue(value)) {
73
124
  return React.createElement("span", { className: "text-muted-foreground" }, "\u2014");
74
125
  }
126
+ if (col.valueMap) {
127
+ const label = col.valueMap[String(value)] ?? String(value);
128
+ return React.createElement("span", { className: "text-sm" }, label);
129
+ }
75
130
  let content;
76
131
  switch (col.type) {
77
132
  case "person":
@@ -109,7 +164,7 @@ function reportColumnsToTanstack(columns, currency) {
109
164
  content = formatDuration(value);
110
165
  break;
111
166
  default:
112
- content = String(value);
167
+ content = plainValue(value);
113
168
  }
114
169
  const cls = alignRight ? "text-right" : alignCenter ? "text-center" : void 0;
115
170
  return cls ? React.createElement("div", { className: cls }, content) : React.createElement(React.Fragment, null, content);
@@ -119,6 +174,6 @@ function reportColumnsToTanstack(columns, currency) {
119
174
  });
120
175
  }
121
176
 
122
- export { formatValuePlain, reportColumnsToTanstack };
123
- //# sourceMappingURL=chunk-5SO5HWV2.js.map
124
- //# sourceMappingURL=chunk-5SO5HWV2.js.map
177
+ export { expandBagColumns, formatValuePlain, reportColumnsToTanstack };
178
+ //# sourceMappingURL=chunk-4QIHTUXD.js.map
179
+ //# sourceMappingURL=chunk-4QIHTUXD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/columns.ts"],"names":["formatCurrencyLocale","formatDateLocale","formatDateTimeLocale"],"mappings":"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","file":"chunk-4QIHTUXD.js","sourcesContent":["import React from 'react'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport type { ReportColumnDef } from '../types'\nimport { PersonLink } from '@fayz-ai/admin'\nimport {\n formatCurrency as formatCurrencyLocale,\n formatDate as formatDateLocale,\n formatDateTime as formatDateTimeLocale,\n} from '@fayz-ai/core'\n\n// ---------------------------------------------------------------------------\n// Formatters (mirrors fieldToColumn.tsx logic)\n// ---------------------------------------------------------------------------\n\nfunction formatCurrency(value: any, currency?: string): string {\n const num = typeof value === 'number' ? value : parseFloat(value)\n if (isNaN(num)) return '—'\n return formatCurrencyLocale(num, currency)\n}\n\nfunction formatDate(value: any): string {\n if (!value) return '—'\n return formatDateLocale(value)\n}\n\nfunction formatDateTime(value: any): string {\n if (!value) return '—'\n return formatDateTimeLocale(value)\n}\n\nfunction formatNumber(value: any): string {\n if (value == null) return '—'\n return typeof value === 'number' ? value.toLocaleString() : String(value)\n}\n\nfunction formatPercent(value: any): string {\n const num = typeof value === 'number' ? value : parseFloat(value)\n return isNaN(num) ? '—' : `${num.toLocaleString(undefined, { maximumFractionDigits: 2 })}%`\n}\n\n/** Durations are stored in HOURS, matching the analytics card renderer. */\nfunction formatDuration(value: any): string {\n const hours = typeof value === 'number' ? value : parseFloat(value)\n if (isNaN(hours) || hours <= 0) return '—'\n const minutes = Math.round(hours * 60)\n if (minutes < 60) return `${minutes}min`\n const h = Math.floor(minutes / 60)\n const m = minutes % 60\n return m ? `${h}h ${m}min` : `${h}h`\n}\n\n// ---------------------------------------------------------------------------\n// Plain-text formatter for exports\n// ---------------------------------------------------------------------------\n\n/** A single cell value as text, whatever shape it arrived in. Arrays join, an\n * open bag prints as \"Chave: valor · Chave: valor\" — never \"[object Object]\",\n * which is what a JSON column looked like in a spreadsheet. */\nexport function plainValue(value: any): string {\n if (value == null || value === '') return ''\n if (Array.isArray(value)) return value.map(plainValue).filter(Boolean).join(', ')\n if (typeof value === 'boolean') return value ? 'Sim' : 'Não'\n if (typeof value === 'object') {\n return Object.entries(value as Record<string, unknown>)\n .filter(([, v]) => v !== null && v !== undefined && v !== '')\n .map(([k, v]) => `${humanizeKey(k)}: ${plainValue(v)}`)\n .join(' · ')\n }\n return String(value)\n}\n\n/** `hair_remaining` / `hairRemaining` → \"Hair remaining\". A key that arrives\n * already humanized (\"Estilo\") comes back unchanged. */\nexport function humanizeKey(key: string): string {\n const spaced = key.replace(/[_-]+/g, ' ').replace(/([a-z\\d])([A-Z])/g, '$1 $2')\n return spaced.charAt(0).toUpperCase() + spaced.slice(1)\n}\n\n/**\n * Expand every `expand` column into one column PER KEY found in the rows, and\n * flatten those keys onto the rows so no consumer needs a special case.\n *\n * The keys cannot be declared on the card — each landing page asks something\n * different, and typing them here would make the NEXT form invisible. So they\n * are discovered from the rows themselves, in first-seen order, and the bag\n * column steps aside. \"Estilo\" as a real column can be read down and sorted;\n * \"Estilo: House · Perfil: Outros\" crammed into one cell cannot, and a raw JSON\n * blob is worse still.\n *\n * Used by the report table AND by the writers, so the screen and the file show\n * the same columns.\n *\n * Columns that are not `expand` pass through untouched, order included.\n */\nexport function expandBagColumns(\n columns: ReportColumnDef[],\n data: Record<string, any>[],\n): { columns: ReportColumnDef[]; rows: Record<string, any>[] } {\n const bags = columns.filter((c) => c.expand)\n if (!bags.length) return { columns, rows: data }\n\n // First-seen order, so the spreadsheet's columns follow the form's own order\n // rather than an alphabetical one nobody asked for.\n const keysByBag = new Map<string, string[]>()\n for (const bag of bags) {\n const keys: string[] = []\n for (const row of data) {\n const value = row?.[bag.key]\n if (!value || typeof value !== 'object' || Array.isArray(value)) continue\n for (const key of Object.keys(value)) if (!keys.includes(key)) keys.push(key)\n }\n keysByBag.set(bag.key, keys)\n }\n\n const expanded = columns.flatMap((col): ReportColumnDef[] => {\n if (!col.expand) return [col]\n return (keysByBag.get(col.key) ?? []).map((key) => ({\n key: `${col.key}.${key}`,\n label: humanizeKey(key),\n type: 'text' as const,\n // The key exists only on the flattened row, never on the read model, so\n // a server-side ORDER BY on it would 42703. Client sort only.\n sortable: false,\n }))\n })\n\n const rows = data.map((row) => {\n const next = { ...row }\n for (const [bagKey, keys] of keysByBag) {\n const bag = row?.[bagKey]\n for (const key of keys) {\n next[`${bagKey}.${key}`] = bag && typeof bag === 'object' ? plainValue(bag[key]) : ''\n }\n }\n return next\n })\n\n return { columns: expanded, rows }\n}\n\nexport function formatValuePlain(value: any, col: ReportColumnDef, currency?: string): string {\n if (value == null || value === '') return ''\n // A stored code is not a word: 'new' in a Situação column is the database\n // talking to itself. Mapped here, so the table and the file agree.\n if (col.valueMap) return col.valueMap[String(value)] ?? String(value)\n if (typeof value === 'object') return plainValue(value)\n switch (col.type) {\n case 'currency':\n return formatCurrency(value, col.currency ?? currency)\n case 'date':\n return formatDate(value)\n case 'datetime':\n return formatDateTime(value)\n case 'number':\n return formatNumber(value)\n case 'percent':\n return formatPercent(value)\n case 'duration':\n return formatDuration(value)\n case 'boolean':\n return value ? 'Yes' : 'No'\n default:\n return String(value)\n }\n}\n\n// ---------------------------------------------------------------------------\n// ReportColumnDef[] → TanStack ColumnDef[]\n// ---------------------------------------------------------------------------\n\nexport function reportColumnsToTanstack(\n columns: ReportColumnDef[],\n currency?: string,\n): ColumnDef<Record<string, any>>[] {\n return columns\n .filter((c) => c.visible !== false)\n .map((col): ColumnDef<Record<string, any>> => {\n const sortable = col.sortable\n ?? ['text', 'number', 'percent', 'duration', 'currency', 'date', 'datetime', 'email'].includes(col.type)\n const alignRight = col.align === 'right'\n || (!col.align && ['currency', 'number', 'percent', 'duration'].includes(col.type))\n const alignCenter = col.align === 'center'\n\n return {\n accessorKey: col.key,\n header: col.label,\n enableSorting: sortable,\n cell: (ctx) => {\n const value = ctx.getValue()\n const row = ctx.row.original\n\n if (col.renderCell) {\n return col.renderCell(value, row)\n }\n\n // An open bag ({} from a lead that answered nothing) is as empty as\n // null — it just does not look like it to a `== null` test.\n if (value == null || value === '' || (typeof value === 'object' && !plainValue(value))) {\n return React.createElement('span', { className: 'text-muted-foreground' }, '—')\n }\n\n if (col.valueMap) {\n const label = col.valueMap[String(value)] ?? String(value)\n return React.createElement('span', { className: 'text-sm' }, label)\n }\n\n let content: React.ReactNode\n switch (col.type) {\n case 'person':\n content = React.createElement(PersonLink, {\n personId: col.idKey ? row[col.idKey] : null,\n name: String(value),\n kind: col.personKind,\n size: 'sm',\n className: 'text-xs',\n })\n break\n case 'currency':\n content = formatCurrency(value, col.currency ?? currency)\n break\n case 'date':\n content = formatDate(value)\n break\n case 'datetime':\n content = formatDateTime(value)\n break\n case 'boolean':\n content = value\n ? React.createElement('svg', { className: 'h-4 w-4 text-success', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: '2' },\n React.createElement('polyline', { points: '20 6 9 17 4 12' }),\n )\n : React.createElement('span', { className: 'text-muted-foreground' }, '—')\n break\n case 'number':\n content = formatNumber(value)\n break\n case 'percent':\n content = formatPercent(value)\n break\n case 'duration':\n content = formatDuration(value)\n break\n default:\n // plainValue, not String(): a jsonb column (form answers, custom\n // fields) rendered as the literal text \"[object Object]\".\n content = plainValue(value)\n }\n\n const cls = alignRight ? 'text-right' : alignCenter ? 'text-center' : undefined\n return cls\n ? React.createElement('div', { className: cls }, content)\n : React.createElement(React.Fragment, null, content)\n },\n meta: { align: alignRight ? 'right' : alignCenter ? 'center' : 'left' },\n }\n })\n}\n"]}
@@ -6,7 +6,13 @@ interface ExportDropdownProps {
6
6
  filename: string;
7
7
  currency?: string;
8
8
  title?: string;
9
+ /**
10
+ * Fetch the WHOLE result set. The table on screen is one page of 50, and a
11
+ * file holding 50 of 4,000 rows says nothing about being a fragment — so the
12
+ * export asks for everything before it writes. Absent ⇒ the page is exported.
13
+ */
14
+ fetchAll?: () => Promise<Record<string, any>[]>;
9
15
  }
10
- export declare function ExportDropdown({ columns, data, filename, currency, title }: ExportDropdownProps): React.JSX.Element;
16
+ export declare function ExportDropdown({ columns, data, filename, currency, title, fetchAll }: ExportDropdownProps): React.JSX.Element;
11
17
  export {};
12
18
  //# sourceMappingURL=ExportDropdown.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ExportDropdown.d.ts","sourceRoot":"","sources":["../../src/components/ExportDropdown.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAA;AAUlD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAG/C,UAAU,mBAAmB;IAC3B,OAAO,EAAE,eAAe,EAAE,CAAA;IAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAA;IAC3B,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,wBAAgB,cAAc,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,mBAAmB,qBAuC/F"}
1
+ {"version":3,"file":"ExportDropdown.d.ts","sourceRoot":"","sources":["../../src/components/ExportDropdown.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAA;AAUlD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAG/C,UAAU,mBAAmB;IAC3B,OAAO,EAAE,eAAe,EAAE,CAAA;IAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAA;IAC3B,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC,CAAA;CAChD;AAED,wBAAgB,cAAc,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,mBAAmB,qBAuDzG"}
@@ -1,12 +1,13 @@
1
- import { formatValuePlain } from './chunk-5SO5HWV2.js';
1
+ import { expandBagColumns, formatValuePlain } from './chunk-4QIHTUXD.js';
2
2
 
3
3
  // src/export/excel.ts
4
4
  var load = (m) => new Function("m", "return import(m)")(m);
5
5
  async function exportToExcel(columns, data, filename, currency) {
6
6
  const XLSX = await load("xlsx");
7
- const visibleCols = columns.filter((c) => c.visible !== false);
7
+ const { columns: cols, rows: expanded } = expandBagColumns(columns, data);
8
+ const visibleCols = cols.filter((c) => c.visible !== false);
8
9
  const header = visibleCols.map((c) => c.label);
9
- const rows = data.map(
10
+ const rows = expanded.map(
10
11
  (row) => visibleCols.map((col) => {
11
12
  const value = row[col.key];
12
13
  if (col.type === "number" || col.type === "currency") {
@@ -31,5 +32,5 @@ async function isExcelAvailable() {
31
32
  }
32
33
 
33
34
  export { exportToExcel, isExcelAvailable };
34
- //# sourceMappingURL=excel-LDM5H5IW.js.map
35
- //# sourceMappingURL=excel-LDM5H5IW.js.map
35
+ //# sourceMappingURL=excel-DSORXL4O.js.map
36
+ //# sourceMappingURL=excel-DSORXL4O.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/export/excel.ts"],"names":[],"mappings":";;;AAKA,IAAM,IAAA,GAAO,CAAC,CAAA,KAAc,IAAI,SAAS,GAAA,EAAK,kBAAkB,EAAE,CAAC,CAAA;AAEnE,eAAsB,aAAA,CACpB,OAAA,EACA,IAAA,EACA,QAAA,EACA,QAAA,EACe;AACf,EAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,MAAM,CAAA;AAE9B,EAAA,MAAM,EAAE,SAAS,IAAA,EAAM,IAAA,EAAM,UAAS,GAAI,gBAAA,CAAiB,SAAS,IAAI,CAAA;AACxE,EAAA,MAAM,cAAc,IAAA,CAAK,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,YAAY,KAAK,CAAA;AAE1D,EAAA,MAAM,SAAS,WAAA,CAAY,GAAA,CAAI,CAAC,CAAA,KAAM,EAAE,KAAK,CAAA;AAC7C,EAAA,MAAM,OAAO,QAAA,CAAS,GAAA;AAAA,IAAI,CAAC,GAAA,KACzB,WAAA,CAAY,GAAA,CAAI,CAAC,GAAA,KAAQ;AACvB,MAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,GAAA,CAAI,GAAG,CAAA;AACzB,MAAA,IAAI,GAAA,CAAI,IAAA,KAAS,QAAA,IAAY,GAAA,CAAI,SAAS,UAAA,EAAY;AACpD,QAAA,MAAM,MAAM,OAAO,KAAA,KAAU,QAAA,GAAW,KAAA,GAAQ,WAAW,KAAK,CAAA;AAChE,QAAA,OAAO,KAAA,CAAM,GAAG,CAAA,GAAI,EAAA,GAAK,GAAA;AAAA,MAC3B;AACA,MAAA,OAAO,gBAAA,CAAiB,KAAA,EAAO,GAAA,EAAK,QAAQ,CAAA;AAAA,IAC9C,CAAC;AAAA,GACH;AAEA,EAAA,MAAM,EAAA,GAAK,KAAK,KAAA,CAAM,YAAA,CAAa,CAAC,MAAA,EAAQ,GAAG,IAAI,CAAC,CAAA;AACpD,EAAA,MAAM,EAAA,GAAK,IAAA,CAAK,KAAA,CAAM,QAAA,EAAS;AAC/B,EAAA,IAAA,CAAK,KAAA,CAAM,iBAAA,CAAkB,EAAA,EAAI,EAAA,EAAI,QAAQ,CAAA;AAC7C,EAAA,IAAA,CAAK,SAAA,CAAU,EAAA,EAAI,CAAA,EAAG,QAAQ,CAAA,KAAA,CAAO,CAAA;AACvC;AAEA,eAAsB,gBAAA,GAAqC;AACzD,EAAA,IAAI;AACF,IAAA,MAAM,KAAK,MAAM,CAAA;AACjB,IAAA,OAAO,IAAA;AAAA,EACT,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,KAAA;AAAA,EACT;AACF","file":"excel-DSORXL4O.js","sourcesContent":["import type { ReportColumnDef } from '../types'\nimport { expandBagColumns, formatValuePlain } from '../lib/columns'\n\n// Opaque dynamic import — hidden from Vite's static analysis so it won't\n// fail when xlsx is not installed (it's an optional peer dependency).\nconst load = (m: string) => new Function('m', 'return import(m)')(m) as Promise<any>\n\nexport async function exportToExcel(\n columns: ReportColumnDef[],\n data: Record<string, any>[],\n filename: string,\n currency?: string,\n): Promise<void> {\n const XLSX = await load('xlsx')\n // Mesma expansão do CSV: uma coluna por resposta do formulário.\n const { columns: cols, rows: expanded } = expandBagColumns(columns, data)\n const visibleCols = cols.filter((c) => c.visible !== false)\n\n const header = visibleCols.map((c) => c.label)\n const rows = expanded.map((row) =>\n visibleCols.map((col) => {\n const value = row[col.key]\n if (col.type === 'number' || col.type === 'currency') {\n const num = typeof value === 'number' ? value : parseFloat(value)\n return isNaN(num) ? '' : num\n }\n return formatValuePlain(value, col, currency)\n }),\n )\n\n const ws = XLSX.utils.aoa_to_sheet([header, ...rows])\n const wb = XLSX.utils.book_new()\n XLSX.utils.book_append_sheet(wb, ws, 'Report')\n XLSX.writeFile(wb, `${filename}.xlsx`)\n}\n\nexport async function isExcelAvailable(): Promise<boolean> {\n try {\n await load('xlsx')\n return true\n } catch {\n return false\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"csv.d.ts","sourceRoot":"","sources":["../../src/export/csv.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAG/C,wBAAgB,WAAW,CACzB,OAAO,EAAE,eAAe,EAAE,EAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,EAC3B,QAAQ,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE,MAAM,GAChB,IAAI,CAUN"}
1
+ {"version":3,"file":"csv.d.ts","sourceRoot":"","sources":["../../src/export/csv.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAG/C,wBAAgB,WAAW,CACzB,OAAO,EAAE,eAAe,EAAE,EAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,EAC3B,QAAQ,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE,MAAM,GAChB,IAAI,CAaN"}
@@ -1 +1 @@
1
- {"version":3,"file":"excel.d.ts","sourceRoot":"","sources":["../../src/export/excel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAO/C,wBAAsB,aAAa,CACjC,OAAO,EAAE,eAAe,EAAE,EAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,EAC3B,QAAQ,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAoBf;AAED,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC,CAOzD"}
1
+ {"version":3,"file":"excel.d.ts","sourceRoot":"","sources":["../../src/export/excel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAO/C,wBAAsB,aAAa,CACjC,OAAO,EAAE,eAAe,EAAE,EAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,EAC3B,QAAQ,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAsBf;AAED,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC,CAOzD"}
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
- import { reportColumnsToTanstack, formatValuePlain } from './chunk-5SO5HWV2.js';
1
+ import { expandBagColumns, reportColumnsToTanstack, formatValuePlain } from './chunk-4QIHTUXD.js';
2
2
  import * as React10 from 'react';
3
3
  import React10__default, { useState, useEffect } from 'react';
4
4
  import { usePluginRuntime, getAnalyticsCards, useTranslation, registerTranslations, createSafeDataProvider, getSupabaseClientOptional, hashRouterAdapter, exportCSV } from '@fayz-ai/core';
5
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';
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
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
 
@@ -1321,35 +1321,47 @@ function ReportsListPage() {
1321
1321
  ] });
1322
1322
  }
1323
1323
  function exportToCsv(columns, data, filename, currency) {
1324
- 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);
1325
1326
  const csvColumns = visibleCols.map((col) => ({
1326
1327
  key: col.key,
1327
1328
  label: col.label,
1328
1329
  format: (value) => formatValuePlain(value, col, currency)
1329
1330
  }));
1330
- exportCSV(csvColumns, data, filename);
1331
+ exportCSV(csvColumns, rows, filename);
1331
1332
  }
1332
- function ExportDropdown({ columns, data, filename, currency, title }) {
1333
+ function ExportDropdown({ columns, data, filename, currency, title, fetchAll }) {
1333
1334
  const t = useTranslation();
1334
1335
  const [excelAvailable, setExcelAvailable] = useState(false);
1335
1336
  const [pdfAvailable, setPdfAvailable] = useState(false);
1337
+ const [busy, setBusy] = useState(false);
1336
1338
  useEffect(() => {
1337
- import('./excel-LDM5H5IW.js').then((m) => m.isExcelAvailable()).then(setExcelAvailable).catch(() => {
1339
+ import('./excel-DSORXL4O.js').then((m) => m.isExcelAvailable()).then(setExcelAvailable).catch(() => {
1338
1340
  });
1339
- import('./pdf-XA2AR6B4.js').then((m) => m.isPdfAvailable()).then(setPdfAvailable).catch(() => {
1341
+ import('./pdf-DH4CJMV5.js').then((m) => m.isPdfAvailable()).then(setPdfAvailable).catch(() => {
1340
1342
  });
1341
1343
  }, []);
1342
- const handleCsv = () => exportToCsv(columns, data, filename, currency);
1343
- const handleExcel = async () => {
1344
- const { exportToExcel } = await import('./excel-LDM5H5IW.js');
1345
- await exportToExcel(columns, data, filename, currency);
1346
- };
1347
- const handlePdf = async () => {
1348
- const { exportToPdf } = await import('./pdf-XA2AR6B4.js');
1349
- 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
+ }
1350
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
+ });
1351
1363
  return /* @__PURE__ */ jsxs(Dropdown, { children: [
1352
- /* @__PURE__ */ jsx(DropdownTrigger, { asChild: true, children: /* @__PURE__ */ jsx(ToolbarAction, { icon: Download, caret: true, children: t("reports.export") }) }),
1364
+ /* @__PURE__ */ jsx(DropdownTrigger, { asChild: true, children: /* @__PURE__ */ jsx(ToolbarAction, { icon: Download, caret: true, disabled: busy, children: t("reports.export") }) }),
1353
1365
  /* @__PURE__ */ jsxs(DropdownContent, { align: "end", children: [
1354
1366
  /* @__PURE__ */ jsx(DropdownItem, { onClick: handleCsv, children: t("reports.exportCsv") }),
1355
1367
  excelAvailable && /* @__PURE__ */ jsx(DropdownItem, { onClick: handleExcel, children: t("reports.exportExcel") }),
@@ -1409,11 +1421,15 @@ function ViewerBody({ card }) {
1409
1421
  const { state, setPage, setPageSize } = useAnalyticsContext();
1410
1422
  const { result, loading, error, engineMissing } = useAnalyticsCard(card);
1411
1423
  const columns = React10__default.useMemo(() => cardColumns(card), [card]);
1424
+ const expanded = React10__default.useMemo(
1425
+ () => expandBagColumns(columns, result?.rows ?? []),
1426
+ [columns, result?.rows]
1427
+ );
1412
1428
  const tanstackColumns = React10__default.useMemo(
1413
- () => reportColumnsToTanstack(columns, currency.code),
1414
- [columns, currency.code]
1429
+ () => reportColumnsToTanstack(expanded.columns, currency.code),
1430
+ [expanded.columns, currency.code]
1415
1431
  );
1416
- const rows = result?.rows ?? [];
1432
+ const rows = expanded.rows;
1417
1433
  if (card.available === false) {
1418
1434
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center rounded-xl border border-dashed py-16 text-center", children: [
1419
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" }) }),
@@ -1504,6 +1520,7 @@ function ViewerActions({ card }) {
1504
1520
  const { currency, labels } = useAnalyticsConfig();
1505
1521
  const { result } = useAnalyticsCard(card);
1506
1522
  const columns = React10__default.useMemo(() => cardColumns(card), [card]);
1523
+ const fetchAll = useAnalyticsCardRows(card);
1507
1524
  if (!result?.rows?.length) return null;
1508
1525
  return /* @__PURE__ */ jsxs(Fragment, { children: [
1509
1526
  /* @__PURE__ */ jsx(
@@ -1511,6 +1528,7 @@ function ViewerActions({ card }) {
1511
1528
  {
1512
1529
  columns,
1513
1530
  data: result.rows,
1531
+ fetchAll,
1514
1532
  filename: card.id,
1515
1533
  currency: currency.code,
1516
1534
  title: card.name