@fayz-ai/plugin-reports 0.9.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/{chunk-LODXZ2SC.js → chunk-2ZFN6ECM.js} +3 -3
- package/dist/chunk-2ZFN6ECM.js.map +1 -0
- package/dist/{ReportsContext.d.ts → context.d.ts} +1 -1
- package/dist/context.d.ts.map +1 -0
- package/dist/data/aggregate.d.ts +14 -0
- package/dist/data/aggregate.d.ts.map +1 -0
- package/dist/data/analytics-mock.d.ts +3 -0
- package/dist/data/analytics-mock.d.ts.map +1 -0
- package/dist/data/analytics-supabase.d.ts +7 -0
- package/dist/data/analytics-supabase.d.ts.map +1 -0
- package/dist/{excel-QHUSM6IH.js → excel-WE73YE2V.js} +3 -3
- package/dist/{excel-QHUSM6IH.js.map → excel-WE73YE2V.js.map} +1 -1
- package/dist/index.d.ts +63 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +972 -650
- package/dist/index.js.map +1 -1
- package/dist/lib/lastViewed.d.ts +5 -0
- package/dist/lib/lastViewed.d.ts.map +1 -0
- package/dist/lib/legacy.d.ts +5 -0
- package/dist/lib/legacy.d.ts.map +1 -0
- package/dist/{pdf-HETKN26Y.js → pdf-E6DXHFXU.js} +3 -3
- package/dist/{pdf-HETKN26Y.js.map → pdf-E6DXHFXU.js.map} +1 -1
- package/dist/views/AnalyticsOverview.d.ts +3 -0
- package/dist/views/AnalyticsOverview.d.ts.map +1 -0
- package/dist/views/AnalyticsReportViewer.d.ts +5 -0
- package/dist/views/AnalyticsReportViewer.d.ts.map +1 -0
- package/dist/views/AnalyticsSurface.d.ts +75 -0
- package/dist/views/AnalyticsSurface.d.ts.map +1 -0
- package/dist/views/LiveViewPage.d.ts +3 -0
- package/dist/views/LiveViewPage.d.ts.map +1 -0
- package/dist/views/ReportsListPage.d.ts +3 -0
- package/dist/views/ReportsListPage.d.ts.map +1 -0
- package/dist/{ReportsPage.d.ts → views/ReportsPage.d.ts} +2 -2
- package/dist/views/ReportsPage.d.ts.map +1 -0
- package/package.json +5 -7
- package/dist/ReportsContext.d.ts.map +0 -1
- package/dist/ReportsPage.d.ts.map +0 -1
- package/dist/chunk-LODXZ2SC.js.map +0 -1
- package/src/ReportsContext.tsx +0 -33
- package/src/ReportsPage.tsx +0 -62
- package/src/components/ExportDropdown.tsx +0 -63
- package/src/components/ReportCard.tsx +0 -38
- package/src/components/ReportFilterBar.tsx +0 -132
- package/src/components/ReportPagination.tsx +0 -59
- package/src/components/ReportSummaryRow.tsx +0 -68
- package/src/data/index.ts +0 -4
- package/src/data/mock.ts +0 -9
- package/src/data/supabase.ts +0 -200
- package/src/data/types.ts +0 -10
- package/src/export/csv.ts +0 -21
- package/src/export/excel.ts +0 -42
- package/src/export/pdf.ts +0 -47
- package/src/hooks/useReportData.ts +0 -123
- package/src/index.ts +0 -142
- package/src/lib/columns.ts +0 -130
- package/src/lib/tenant.ts +0 -3
- package/src/locales/en.ts +0 -28
- package/src/locales/index.ts +0 -7
- package/src/locales/pt-BR.ts +0 -28
- package/src/types.ts +0 -171
- package/src/views/ReportHub.tsx +0 -97
- package/src/views/ReportViewer.tsx +0 -182
package/dist/index.js
CHANGED
|
@@ -1,367 +1,725 @@
|
|
|
1
|
-
import { reportColumnsToTanstack, formatValuePlain } from './chunk-
|
|
2
|
-
import
|
|
3
|
-
import
|
|
1
|
+
import { reportColumnsToTanstack, formatValuePlain } from './chunk-2ZFN6ECM.js';
|
|
2
|
+
import * as React4 from 'react';
|
|
3
|
+
import React4__default, { useState, useEffect } from 'react';
|
|
4
|
+
import { usePluginRuntime, getAnalyticsCards, registerTranslations, createSafeDataProvider, getSupabaseClientOptional, hashRouterAdapter, useTranslation, exportCSV } from '@fayz-ai/core';
|
|
5
|
+
import { AnalyticsContextProvider, collectFilters, AnalyticsControls, AnalyticsGrid, Card, FilterChip, DataTable, Button, SubpageHeader, useAnalyticsCard, useAnalyticsContext, AnalyticsCard, Dropdown, DropdownTrigger, DropdownContent, DropdownItem } from '@fayz-ai/ui';
|
|
4
6
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
5
|
-
import {
|
|
6
|
-
import { Button, DataTable, Input, DatePicker, Dropdown, DropdownTrigger, DropdownContent, DropdownItem, getLucideIcon, Badge, Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '@fayz-ai/ui';
|
|
7
|
-
import { dedup } from '@fayz-ai/saas';
|
|
7
|
+
import { BarChart3, User, Search, ArrowLeft, Printer, Construction, Download, Radio, ChevronLeft, ChevronRight } from 'lucide-react';
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
function
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
return
|
|
9
|
+
// src/data/aggregate.ts
|
|
10
|
+
function num(value) {
|
|
11
|
+
if (value == null) return 0;
|
|
12
|
+
const n = typeof value === "number" ? value : Number(value);
|
|
13
|
+
return Number.isFinite(n) ? n : 0;
|
|
14
14
|
}
|
|
15
|
-
function
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
onClick: () => onClick(report),
|
|
29
|
-
className: "flex flex-col items-start gap-2 rounded-xl border bg-card p-4 text-left transition-all hover:border-primary/30 hover:shadow-md",
|
|
30
|
-
children: [
|
|
31
|
-
/* @__PURE__ */ jsxs("div", { className: "flex w-full items-start justify-between", children: [
|
|
32
|
-
/* @__PURE__ */ jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10 text-primary", children: /* @__PURE__ */ jsx(IconComp, { className: "h-5 w-5" }) }),
|
|
33
|
-
report.badge && /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "text-xs capitalize", children: report.badge })
|
|
34
|
-
] }),
|
|
35
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
36
|
-
/* @__PURE__ */ jsx("h4", { className: "font-medium text-sm", children: report.name }),
|
|
37
|
-
report.description && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-0.5 line-clamp-2", children: report.description })
|
|
38
|
-
] })
|
|
39
|
-
]
|
|
15
|
+
function truncate(value, grain) {
|
|
16
|
+
const d = new Date(String(value));
|
|
17
|
+
if (Number.isNaN(d.getTime())) return null;
|
|
18
|
+
const out = new Date(d);
|
|
19
|
+
switch (grain) {
|
|
20
|
+
case "hour":
|
|
21
|
+
out.setMinutes(0, 0, 0);
|
|
22
|
+
break;
|
|
23
|
+
case "week": {
|
|
24
|
+
const day = (out.getDay() + 6) % 7;
|
|
25
|
+
out.setDate(out.getDate() - day);
|
|
26
|
+
out.setHours(0, 0, 0, 0);
|
|
27
|
+
break;
|
|
40
28
|
}
|
|
41
|
-
|
|
29
|
+
case "month":
|
|
30
|
+
out.setDate(1);
|
|
31
|
+
out.setHours(0, 0, 0, 0);
|
|
32
|
+
break;
|
|
33
|
+
case "quarter":
|
|
34
|
+
out.setMonth(Math.floor(out.getMonth() / 3) * 3, 1);
|
|
35
|
+
out.setHours(0, 0, 0, 0);
|
|
36
|
+
break;
|
|
37
|
+
case "year":
|
|
38
|
+
out.setMonth(0, 1);
|
|
39
|
+
out.setHours(0, 0, 0, 0);
|
|
40
|
+
break;
|
|
41
|
+
default:
|
|
42
|
+
out.setHours(0, 0, 0, 0);
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
return out.toISOString();
|
|
42
46
|
}
|
|
43
|
-
function
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const map = /* @__PURE__ */ new Map();
|
|
52
|
-
for (const report of filtered) {
|
|
53
|
-
const list = map.get(report.category) ?? [];
|
|
54
|
-
list.push(report);
|
|
55
|
-
map.set(report.category, list);
|
|
56
|
-
}
|
|
57
|
-
const groups = [];
|
|
58
|
-
for (const [name, reports] of map) {
|
|
59
|
-
groups.push({ name, reports });
|
|
47
|
+
function matchesFilters(row, filters) {
|
|
48
|
+
for (const [key, value] of Object.entries(filters)) {
|
|
49
|
+
if (value == null || value === "") continue;
|
|
50
|
+
const actual = row[key];
|
|
51
|
+
if (Array.isArray(value)) {
|
|
52
|
+
if (value.length > 0 && !value.map(String).includes(String(actual))) return false;
|
|
53
|
+
} else if (String(actual) !== String(value)) {
|
|
54
|
+
return false;
|
|
60
55
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
Input,
|
|
73
|
-
{
|
|
74
|
-
value: search,
|
|
75
|
-
onChange: (e) => setSearch(e.target.value),
|
|
76
|
-
placeholder: t2("reports.search"),
|
|
77
|
-
className: "pl-9"
|
|
78
|
-
}
|
|
79
|
-
)
|
|
80
|
-
] }),
|
|
81
|
-
/* @__PURE__ */ jsx("div", { className: "space-y-8", children: categories.map((category) => /* @__PURE__ */ jsxs("div", { className: "rounded-xl border bg-card shadow-sm p-5", children: [
|
|
82
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-4", children: [
|
|
83
|
-
/* @__PURE__ */ jsx("h2", { className: "font-semibold", children: category.name }),
|
|
84
|
-
/* @__PURE__ */ jsxs("span", { className: "text-sm text-muted-foreground", children: [
|
|
85
|
-
category.reports.length,
|
|
86
|
-
" ",
|
|
87
|
-
t2("reports.reports")
|
|
88
|
-
] })
|
|
89
|
-
] }),
|
|
90
|
-
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3", children: category.reports.map((report) => /* @__PURE__ */ jsx(ReportCard, { report, onClick: onSelect }, report.id)) })
|
|
91
|
-
] }, category.name)) }),
|
|
92
|
-
categories.length === 0 && search && /* @__PURE__ */ jsx("div", { className: "text-center py-12 text-muted-foreground", children: t2("reports.noResults") })
|
|
93
|
-
] });
|
|
56
|
+
}
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
function inRange(row, dateColumn, from, to) {
|
|
60
|
+
const raw = row[dateColumn];
|
|
61
|
+
if (raw == null) return true;
|
|
62
|
+
const t2 = new Date(String(raw)).getTime();
|
|
63
|
+
if (Number.isNaN(t2)) return true;
|
|
64
|
+
if (from && t2 < (/* @__PURE__ */ new Date(`${from}T00:00:00`)).getTime()) return false;
|
|
65
|
+
if (to && t2 > (/* @__PURE__ */ new Date(`${to}T23:59:59.999`)).getTime()) return false;
|
|
66
|
+
return true;
|
|
94
67
|
}
|
|
95
|
-
function
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
68
|
+
function applyMeasure(rows, agg, column) {
|
|
69
|
+
if (agg === "count") return rows.length;
|
|
70
|
+
const values = rows.map((r) => r[column]).filter((v) => v != null);
|
|
71
|
+
if (agg === "count_distinct") return new Set(values.map(String)).size;
|
|
72
|
+
const numbers = values.map(num);
|
|
73
|
+
if (numbers.length === 0) return agg === "sum" ? 0 : null;
|
|
74
|
+
switch (agg) {
|
|
75
|
+
case "sum":
|
|
76
|
+
return numbers.reduce((a, b) => a + b, 0);
|
|
77
|
+
case "avg":
|
|
78
|
+
return numbers.reduce((a, b) => a + b, 0) / numbers.length;
|
|
79
|
+
case "min":
|
|
80
|
+
return Math.min(...numbers);
|
|
81
|
+
case "max":
|
|
82
|
+
return Math.max(...numbers);
|
|
83
|
+
default:
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
100
86
|
}
|
|
101
|
-
function
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
87
|
+
function aggregateRows(source, card, state, options) {
|
|
88
|
+
const snapshot = card.query.dateColumn === null;
|
|
89
|
+
const dateColumn = card.query.dateColumn ?? "created_at";
|
|
90
|
+
const dimensions = card.query.dimensions ?? [];
|
|
91
|
+
const measures = (card.query.measures ?? []).filter((m) => m.agg !== "ratio");
|
|
92
|
+
const filtered = source.filter((row) => snapshot || inRange(row, dateColumn, state.dateRange?.from, state.dateRange?.to)).filter((row) => matchesFilters(row, state.filters ?? {}));
|
|
93
|
+
const summary = {};
|
|
94
|
+
for (const m of measures) {
|
|
95
|
+
const value = applyMeasure(filtered, m.agg, m.column ?? m.key);
|
|
96
|
+
summary[m.key] = value ?? 0;
|
|
97
|
+
}
|
|
98
|
+
if (measures.length === 0 && dimensions.length === 0) {
|
|
99
|
+
const sorted = sortRows(filtered, state.sortBy ?? card.query.defaultSort, state.sortDir ?? card.query.defaultSortDir);
|
|
100
|
+
return { rows: page(sorted, state, options), total: sorted.length, summary };
|
|
101
|
+
}
|
|
102
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
103
|
+
for (const row of filtered) {
|
|
104
|
+
const keyParts = {};
|
|
105
|
+
for (const d of dimensions) {
|
|
106
|
+
const raw = row[d.key];
|
|
107
|
+
keyParts[d.key] = d.grain && d.grain !== "auto" ? truncate(raw, d.grain) : raw ?? null;
|
|
108
|
+
}
|
|
109
|
+
const id = JSON.stringify(dimensions.map((d) => keyParts[d.key]));
|
|
110
|
+
const existing = buckets.get(id);
|
|
111
|
+
if (existing) existing.rows.push(row);
|
|
112
|
+
else buckets.set(id, { key: keyParts, rows: [row] });
|
|
105
113
|
}
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
dateRange: defaultDateRange(),
|
|
111
|
-
filters: buildDefaultFilters(report),
|
|
112
|
-
sortBy: report.dataSource.defaultSort,
|
|
113
|
-
sortDir: report.dataSource.defaultSortDir ?? "desc",
|
|
114
|
-
page: 1,
|
|
115
|
-
pageSize: defaultPageSize
|
|
114
|
+
let grouped = [...buckets.values()].map(({ key, rows }) => {
|
|
115
|
+
const out = { ...key };
|
|
116
|
+
for (const m of measures) out[m.key] = applyMeasure(rows, m.agg, m.column ?? m.key);
|
|
117
|
+
return out;
|
|
116
118
|
});
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
119
|
+
grouped = sortRows(grouped, state.sortBy ?? card.query.defaultSort, state.sortDir ?? card.query.defaultSortDir);
|
|
120
|
+
if (card.query.limit) grouped = grouped.slice(0, card.query.limit);
|
|
121
|
+
return { rows: page(grouped, state, options), total: grouped.length, summary };
|
|
122
|
+
}
|
|
123
|
+
function sortRows(rows, sortBy, dir) {
|
|
124
|
+
if (!sortBy) return rows;
|
|
125
|
+
const sign = dir === "asc" ? 1 : -1;
|
|
126
|
+
return [...rows].sort((a, b) => {
|
|
127
|
+
const av = a[sortBy];
|
|
128
|
+
const bv = b[sortBy];
|
|
129
|
+
if (av == null && bv == null) return 0;
|
|
130
|
+
if (av == null) return 1;
|
|
131
|
+
if (bv == null) return -1;
|
|
132
|
+
if (typeof av === "number" || typeof bv === "number") return (num(av) - num(bv)) * sign;
|
|
133
|
+
return String(av).localeCompare(String(bv)) * sign;
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
function page(rows, state, options) {
|
|
137
|
+
if (options?.all) return rows;
|
|
138
|
+
const size = state.pageSize || 50;
|
|
139
|
+
const start = ((state.page || 1) - 1) * size;
|
|
140
|
+
return rows.slice(start, start + size);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/data/analytics-supabase.ts
|
|
144
|
+
function snakeToCamel(s) {
|
|
145
|
+
return s.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
146
|
+
}
|
|
147
|
+
function mapRow(row, reserved, columnMap) {
|
|
148
|
+
const out = {};
|
|
149
|
+
for (const [key, value] of Object.entries(row)) {
|
|
150
|
+
if (reserved.has(key)) {
|
|
151
|
+
out[key] = value;
|
|
152
|
+
continue;
|
|
138
153
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
154
|
+
out[columnMap?.[key] ?? snakeToCamel(key)] = value;
|
|
155
|
+
if (!(key in out)) out[key] = value;
|
|
156
|
+
}
|
|
157
|
+
return out;
|
|
158
|
+
}
|
|
159
|
+
function reservedKeys(card) {
|
|
160
|
+
return /* @__PURE__ */ new Set([
|
|
161
|
+
...(card.query.dimensions ?? []).map((d) => d.key),
|
|
162
|
+
...(card.query.measures ?? []).map((m) => m.key)
|
|
163
|
+
]);
|
|
164
|
+
}
|
|
165
|
+
function searchColumns(card) {
|
|
166
|
+
const cols = (card.columns ?? []).filter((c) => c.type === "text" || c.type === "email").map((c) => c.key);
|
|
167
|
+
return cols.length ? cols : void 0;
|
|
168
|
+
}
|
|
169
|
+
function camelToSnake(s) {
|
|
170
|
+
return s.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
|
|
171
|
+
}
|
|
172
|
+
async function selectFromView(card, state, db, tenantId, reserved) {
|
|
173
|
+
const { source } = card.query;
|
|
174
|
+
const size = state.pageSize || 50;
|
|
175
|
+
let q = db.from(source.name).select("*", { count: "exact" });
|
|
176
|
+
if (tenantId) q = q.eq(source.tenantColumn ?? "tenant_id", tenantId);
|
|
177
|
+
const snapshot = card.query.dateColumn === null;
|
|
178
|
+
const dateCol = card.query.dateColumn ?? "created_at";
|
|
179
|
+
if (!snapshot && state.dateRange?.from) q = q.gte(dateCol, state.dateRange.from);
|
|
180
|
+
if (!snapshot && state.dateRange?.to) {
|
|
181
|
+
q = q.lte(dateCol, dateCol.endsWith("_at") ? `${state.dateRange.to}T23:59:59.999` : state.dateRange.to);
|
|
182
|
+
}
|
|
183
|
+
for (const [key, value] of Object.entries(state.filters ?? {})) {
|
|
184
|
+
if (value == null || value === "") continue;
|
|
185
|
+
const col = camelToSnake(key);
|
|
186
|
+
q = Array.isArray(value) ? q.in(col, value) : q.eq(col, value);
|
|
187
|
+
}
|
|
188
|
+
const searchable = searchColumns(card);
|
|
189
|
+
if (state.search?.trim() && searchable?.length) {
|
|
190
|
+
const term = `%${state.search.trim()}%`;
|
|
191
|
+
q = q.or(searchable.map((c) => `${camelToSnake(c)}.ilike.${term}`).join(","));
|
|
192
|
+
}
|
|
193
|
+
const sortBy = state.sortBy ?? card.query.defaultSort;
|
|
194
|
+
if (sortBy) {
|
|
195
|
+
q = q.order(camelToSnake(sortBy), {
|
|
196
|
+
ascending: (state.sortDir ?? card.query.defaultSortDir ?? "desc") === "asc"
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
const from = ((state.page || 1) - 1) * size;
|
|
200
|
+
const { data, error, count } = await q.range(from, from + size - 1);
|
|
201
|
+
if (error) throw error;
|
|
166
202
|
return {
|
|
167
|
-
|
|
168
|
-
total:
|
|
169
|
-
loading,
|
|
170
|
-
generated,
|
|
171
|
-
error,
|
|
172
|
-
query,
|
|
173
|
-
generate,
|
|
174
|
-
setDateRange,
|
|
175
|
-
setFilter,
|
|
176
|
-
setSort,
|
|
177
|
-
setPage,
|
|
178
|
-
setPageSize,
|
|
179
|
-
setSearch
|
|
203
|
+
rows: (data ?? []).map((r) => mapRow(r, reserved, source.columnMap)),
|
|
204
|
+
total: count ?? 0
|
|
180
205
|
};
|
|
181
206
|
}
|
|
182
|
-
function
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
)
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
207
|
+
function createSupabaseAnalyticsProvider(config) {
|
|
208
|
+
const resolveTenantId = () => (typeof config.tenantId === "function" ? config.tenantId() : config.tenantId) || void 0;
|
|
209
|
+
function client() {
|
|
210
|
+
const supabase = getSupabaseClientOptional();
|
|
211
|
+
if (!supabase) throw new Error("Supabase client not available");
|
|
212
|
+
return config.schema && config.schema !== "public" ? supabase.schema(config.schema) : supabase;
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
async run(card, state) {
|
|
216
|
+
const { source } = card.query;
|
|
217
|
+
const reserved = reservedKeys(card);
|
|
218
|
+
if (source.kind === "fn") {
|
|
219
|
+
if (!source.run) throw new Error(`analytics: card ${card.id} declares kind 'fn' with no run()`);
|
|
220
|
+
const rows2 = await source.run({
|
|
221
|
+
tenantId: resolveTenantId(),
|
|
222
|
+
dateRange: state.dateRange,
|
|
223
|
+
filters: state.filters
|
|
224
|
+
});
|
|
225
|
+
return aggregateRows(rows2, card, state);
|
|
226
|
+
}
|
|
227
|
+
if (!source.name) throw new Error(`analytics: card ${card.id} has no source name`);
|
|
228
|
+
const isDetail = (card.query.dimensions?.length ?? 0) === 0 && (card.query.measures?.length ?? 0) === 0;
|
|
229
|
+
if (source.kind === "view" && isDetail) {
|
|
230
|
+
return selectFromView(card, state, client(), resolveTenantId(), reserved);
|
|
231
|
+
}
|
|
232
|
+
if (source.kind === "rpc") {
|
|
233
|
+
const { data: data2, error: error2 } = await client().rpc(source.name, {
|
|
234
|
+
p_tenant_id: resolveTenantId(),
|
|
235
|
+
p_from: state.dateRange.from,
|
|
236
|
+
p_to: state.dateRange.to,
|
|
237
|
+
...Object.fromEntries(Object.entries(state.filters ?? {}).map(([k, v]) => [`p_${k}`, v]))
|
|
238
|
+
});
|
|
239
|
+
if (error2) throw error2;
|
|
240
|
+
const rows2 = Array.isArray(data2) ? data2 : data2?.rows ?? [];
|
|
241
|
+
return {
|
|
242
|
+
rows: rows2.map((r) => mapRow(r, reserved, source.columnMap)),
|
|
243
|
+
total: Array.isArray(data2) ? rows2.length : data2?.total ?? rows2.length,
|
|
244
|
+
summary: Array.isArray(data2) ? void 0 : data2?.summary
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
const size = state.pageSize || 50;
|
|
248
|
+
const snapshot = card.query.dateColumn === null;
|
|
249
|
+
const { data, error } = await client().rpc("analytics_run", {
|
|
250
|
+
p_source: source.name,
|
|
251
|
+
p_tenant_id: resolveTenantId() ?? null,
|
|
252
|
+
p_from: !snapshot && state.dateRange.from ? `${state.dateRange.from}T00:00:00` : null,
|
|
253
|
+
// Inclusive end-of-day: the range picker speaks in days, the column is
|
|
254
|
+
// a timestamp, and closing at midnight loses everything sold that day.
|
|
255
|
+
p_to: !snapshot && state.dateRange.to ? `${state.dateRange.to}T23:59:59.999` : null,
|
|
256
|
+
p_dimensions: (card.query.dimensions ?? []).map((d) => ({
|
|
257
|
+
key: d.key,
|
|
258
|
+
...d.grain && d.grain !== "auto" ? { grain: d.grain } : {}
|
|
259
|
+
})),
|
|
260
|
+
p_measures: (card.query.measures ?? []).filter((m) => m.agg !== "ratio").map((m) => ({ key: m.key, agg: m.agg, ...m.column ? { column: m.column } : {} })),
|
|
261
|
+
p_filters: state.filters ?? {},
|
|
262
|
+
p_search: state.search ?? null,
|
|
263
|
+
p_search_columns: searchColumns(card) ?? null,
|
|
264
|
+
p_sort: state.sortBy ?? card.query.defaultSort ?? null,
|
|
265
|
+
p_dir: state.sortDir ?? card.query.defaultSortDir ?? "desc",
|
|
266
|
+
p_limit: card.query.limit ?? size,
|
|
267
|
+
p_offset: card.query.limit ? 0 : ((state.page || 1) - 1) * size
|
|
268
|
+
});
|
|
269
|
+
if (error) throw error;
|
|
270
|
+
const rows = data?.rows ?? [];
|
|
271
|
+
return {
|
|
272
|
+
rows: rows.map((r) => mapRow(r, reserved, source.columnMap)),
|
|
273
|
+
total: data?.total ?? rows.length,
|
|
274
|
+
summary: data?.summary ?? void 0
|
|
275
|
+
};
|
|
235
276
|
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
] })
|
|
246
|
-
] })
|
|
247
|
-
] });
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// src/data/analytics-mock.ts
|
|
281
|
+
function seeded(seed) {
|
|
282
|
+
let h = 2166136261;
|
|
283
|
+
for (let i = 0; i < seed.length; i++) {
|
|
284
|
+
h ^= seed.charCodeAt(i);
|
|
285
|
+
h = Math.imul(h, 16777619);
|
|
248
286
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
287
|
+
return () => {
|
|
288
|
+
h += 1831565813;
|
|
289
|
+
let t2 = h;
|
|
290
|
+
t2 = Math.imul(t2 ^ t2 >>> 15, t2 | 1);
|
|
291
|
+
t2 ^= t2 + Math.imul(t2 ^ t2 >>> 7, t2 | 61);
|
|
292
|
+
return ((t2 ^ t2 >>> 14) >>> 0) / 4294967296;
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
var SAMPLE_LABELS = {
|
|
296
|
+
product_name: ["Camiseta Preta", "Caneca Fayz", "Moletom", "Bon\xE9", "Adesivos"],
|
|
297
|
+
category_name: ["Vestu\xE1rio", "Acess\xF3rios", "Casa"],
|
|
298
|
+
customer_name: ["Ana Souza", "Bruno Lima", "Carla Dias", "Diego Alves"],
|
|
299
|
+
status: ["open", "archived", "cancelled"],
|
|
300
|
+
financial_status: ["paid", "pending", "refunded"],
|
|
301
|
+
fulfillment_status: ["fulfilled", "unfulfilled", "partially_fulfilled"]
|
|
302
|
+
};
|
|
303
|
+
function labelsFor(key, rand) {
|
|
304
|
+
const known = SAMPLE_LABELS[key];
|
|
305
|
+
if (known) return known;
|
|
306
|
+
const n = 3 + Math.floor(rand() * 3);
|
|
307
|
+
return Array.from({ length: n }, (_, i) => `${key.replace(/_/g, " ")} ${i + 1}`);
|
|
308
|
+
}
|
|
309
|
+
function createMockAnalyticsProvider() {
|
|
310
|
+
return {
|
|
311
|
+
async run(card, state) {
|
|
312
|
+
const rand = seeded(card.id);
|
|
313
|
+
const dateColumn = card.query.dateColumn ?? "created_at";
|
|
314
|
+
const dimensions = card.query.dimensions ?? [];
|
|
315
|
+
const measures = card.query.measures ?? [];
|
|
316
|
+
const from = (/* @__PURE__ */ new Date(`${state.dateRange.from}T12:00:00`)).getTime();
|
|
317
|
+
const to = (/* @__PURE__ */ new Date(`${state.dateRange.to}T12:00:00`)).getTime();
|
|
318
|
+
const span = Math.max(to - from, 864e5);
|
|
319
|
+
const rowCount = Math.min(400, Math.max(60, Math.round(span / 864e5) * 4));
|
|
320
|
+
const categoricalDims = dimensions.filter((d) => !d.grain && d.key !== dateColumn);
|
|
321
|
+
const labelPools = new Map(categoricalDims.map((d) => [d.key, labelsFor(d.key, rand)]));
|
|
322
|
+
const rows = Array.from({ length: rowCount }, () => {
|
|
323
|
+
const row = {
|
|
324
|
+
[dateColumn]: new Date(from + rand() * span).toISOString()
|
|
325
|
+
};
|
|
326
|
+
for (const [key, pool] of labelPools) {
|
|
327
|
+
row[key] = pool[Math.floor(rand() * pool.length)];
|
|
259
328
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
265
|
-
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: filter.label }),
|
|
266
|
-
/* @__PURE__ */ jsx(
|
|
267
|
-
Input,
|
|
268
|
-
{
|
|
269
|
-
type: "number",
|
|
270
|
-
value: value ?? "",
|
|
271
|
-
onChange: (e) => onChange(e.target.value ? Number(e.target.value) : void 0),
|
|
272
|
-
placeholder: filter.placeholder ?? filter.label,
|
|
273
|
-
className: "w-[120px]"
|
|
329
|
+
for (const m of measures) {
|
|
330
|
+
if (m.agg === "count" || m.agg === "ratio") continue;
|
|
331
|
+
const column = m.column ?? m.key;
|
|
332
|
+
row[column] = m.format === "currency" ? Math.round(rand() * 4e4) / 100 : m.format === "percent" ? Math.round(rand() * 1e4) / 100 : Math.round(rand() * 50);
|
|
274
333
|
}
|
|
275
|
-
|
|
276
|
-
|
|
334
|
+
for (const c of card.columns ?? []) {
|
|
335
|
+
if (c.key in row) continue;
|
|
336
|
+
row[c.key] = c.type === "currency" || c.type === "number" ? Math.round(rand() * 1e4) / 100 : c.type === "date" ? new Date(from + rand() * span).toISOString() : `${c.label} ${Math.ceil(rand() * 20)}`;
|
|
337
|
+
}
|
|
338
|
+
return row;
|
|
339
|
+
});
|
|
340
|
+
return aggregateRows(rows, card, state);
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// src/lib/tenant.ts
|
|
346
|
+
var t;
|
|
347
|
+
function getReportsTenantId() {
|
|
348
|
+
return t;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// src/locales/en.ts
|
|
352
|
+
var en = {
|
|
353
|
+
"reports.pageTitle": "Reports",
|
|
354
|
+
"reports.pageSubtitle": "Access complete reports for analysis and decision making",
|
|
355
|
+
"reports.allReports": "All Reports",
|
|
356
|
+
"reports.dateRange": "Date Range",
|
|
357
|
+
"reports.from": "From",
|
|
358
|
+
"reports.to": "To",
|
|
359
|
+
"reports.export": "Export",
|
|
360
|
+
"reports.exportCsv": "Export CSV",
|
|
361
|
+
"reports.exportExcel": "Export Excel",
|
|
362
|
+
"reports.exportPdf": "Export PDF",
|
|
363
|
+
"reports.noResults": "No results found.",
|
|
364
|
+
"reports.backToReports": "Back",
|
|
365
|
+
"reports.reports": "reports",
|
|
366
|
+
"reports.search": "Search reports...",
|
|
367
|
+
"reports.today": "Today",
|
|
368
|
+
"reports.yesterday": "Yesterday",
|
|
369
|
+
"reports.last7Days": "Last 7 days",
|
|
370
|
+
"reports.thisMonth": "This month",
|
|
371
|
+
"reports.lastMonth": "Last month",
|
|
372
|
+
"reports.thisQuarter": "This quarter",
|
|
373
|
+
"reports.custom": "Custom",
|
|
374
|
+
"reports.generate": "Generate",
|
|
375
|
+
"reports.emptyStateTitle": "Ready to generate",
|
|
376
|
+
"reports.emptyStateDescription": "Select a date range and filters, then click Generate to view the report.",
|
|
377
|
+
"reports.unavailableTitle": "Coming soon",
|
|
378
|
+
"reports.unavailableDescription": "This report is not available yet. It will be enabled in a future update."
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
// src/locales/pt-BR.ts
|
|
382
|
+
var ptBR = {
|
|
383
|
+
"reports.pageTitle": "Relat\xF3rios",
|
|
384
|
+
"reports.pageSubtitle": "Acesse relat\xF3rios completos para an\xE1lise e tomada de decis\xE3o",
|
|
385
|
+
"reports.allReports": "Todos os Relat\xF3rios",
|
|
386
|
+
"reports.dateRange": "Per\xEDodo",
|
|
387
|
+
"reports.from": "De",
|
|
388
|
+
"reports.to": "At\xE9",
|
|
389
|
+
"reports.export": "Exportar",
|
|
390
|
+
"reports.exportCsv": "Exportar CSV",
|
|
391
|
+
"reports.exportExcel": "Exportar Excel",
|
|
392
|
+
"reports.exportPdf": "Exportar PDF",
|
|
393
|
+
"reports.noResults": "Nenhum resultado encontrado.",
|
|
394
|
+
"reports.backToReports": "Voltar",
|
|
395
|
+
"reports.reports": "relat\xF3rios",
|
|
396
|
+
"reports.search": "Buscar relat\xF3rios...",
|
|
397
|
+
"reports.today": "Hoje",
|
|
398
|
+
"reports.yesterday": "Ontem",
|
|
399
|
+
"reports.last7Days": "\xDAltimos 7 dias",
|
|
400
|
+
"reports.thisMonth": "Este m\xEAs",
|
|
401
|
+
"reports.lastMonth": "M\xEAs passado",
|
|
402
|
+
"reports.thisQuarter": "Este trimestre",
|
|
403
|
+
"reports.custom": "Personalizado",
|
|
404
|
+
"reports.generate": "Gerar",
|
|
405
|
+
"reports.emptyStateTitle": "Pronto para gerar",
|
|
406
|
+
"reports.emptyStateDescription": "Selecione o per\xEDodo e os filtros desejados, depois clique em Gerar para visualizar o relat\xF3rio.",
|
|
407
|
+
"reports.unavailableTitle": "Em breve",
|
|
408
|
+
"reports.unavailableDescription": "Este relat\xF3rio ainda n\xE3o est\xE1 dispon\xEDvel. Ser\xE1 habilitado em uma atualiza\xE7\xE3o futura."
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
// src/locales/index.ts
|
|
412
|
+
var reportsLocales = {
|
|
413
|
+
en,
|
|
414
|
+
"pt-BR": ptBR
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
// src/lib/legacy.ts
|
|
418
|
+
function reportDefToCard(report, createdBy) {
|
|
419
|
+
return {
|
|
420
|
+
id: report.id,
|
|
421
|
+
name: report.name,
|
|
422
|
+
description: report.description,
|
|
423
|
+
icon: report.icon,
|
|
424
|
+
category: report.category,
|
|
425
|
+
createdBy,
|
|
426
|
+
available: report.available,
|
|
427
|
+
grain: report.grain,
|
|
428
|
+
canonicalOwner: report.canonicalOwner,
|
|
429
|
+
permission: report.permission,
|
|
430
|
+
query: {
|
|
431
|
+
source: {
|
|
432
|
+
kind: report.dataSource.kind,
|
|
433
|
+
name: report.dataSource.name,
|
|
434
|
+
columnMap: report.dataSource.columnMap
|
|
435
|
+
},
|
|
436
|
+
dateColumn: report.dataSource.dateColumn,
|
|
437
|
+
defaultSort: report.dataSource.defaultSort,
|
|
438
|
+
defaultSortDir: report.dataSource.defaultSortDir,
|
|
439
|
+
filters: (report.filters ?? []).map((f) => ({
|
|
440
|
+
key: f.key,
|
|
441
|
+
label: f.label,
|
|
442
|
+
type: f.type,
|
|
443
|
+
options: f.options,
|
|
444
|
+
defaultValue: f.defaultValue,
|
|
445
|
+
placeholder: f.placeholder,
|
|
446
|
+
// Legacy filters declared no category. Grouping them under the report's
|
|
447
|
+
// own category keeps the picker organised instead of dumping every
|
|
448
|
+
// filter from every report into one flat list.
|
|
449
|
+
group: report.category
|
|
450
|
+
}))
|
|
451
|
+
},
|
|
452
|
+
// No dimensions/measures: a legacy report is a detail listing, and inventing
|
|
453
|
+
// aggregations for it would change what the numbers mean.
|
|
454
|
+
viz: { type: "table" },
|
|
455
|
+
columns: report.columns.map((c) => ({
|
|
456
|
+
key: c.key,
|
|
457
|
+
label: c.label,
|
|
458
|
+
type: c.type,
|
|
459
|
+
sortable: c.sortable,
|
|
460
|
+
currency: c.currency,
|
|
461
|
+
idKey: c.idKey,
|
|
462
|
+
renderCell: c.renderCell,
|
|
463
|
+
visible: c.visible,
|
|
464
|
+
align: c.align,
|
|
465
|
+
aggregate: c.aggregate
|
|
466
|
+
}))
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
function reportDefsToCards(reports, createdBy) {
|
|
470
|
+
return reports.map((r) => reportDefToCard(r, createdBy));
|
|
471
|
+
}
|
|
472
|
+
var KEY = "fayz.analytics.lastViewed";
|
|
473
|
+
function read() {
|
|
474
|
+
if (typeof window === "undefined") return {};
|
|
475
|
+
try {
|
|
476
|
+
const raw = window.localStorage.getItem(KEY);
|
|
477
|
+
return raw ? JSON.parse(raw) : {};
|
|
478
|
+
} catch {
|
|
479
|
+
return {};
|
|
277
480
|
}
|
|
278
|
-
return null;
|
|
279
481
|
}
|
|
280
|
-
function
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
482
|
+
function write(map) {
|
|
483
|
+
if (typeof window === "undefined") return;
|
|
484
|
+
try {
|
|
485
|
+
window.localStorage.setItem(KEY, JSON.stringify(map));
|
|
486
|
+
} catch {
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
490
|
+
function markViewed(cardId) {
|
|
491
|
+
const next = { ...read(), [cardId]: (/* @__PURE__ */ new Date()).toISOString() };
|
|
492
|
+
write(next);
|
|
493
|
+
for (const listener of listeners) listener(next);
|
|
494
|
+
}
|
|
495
|
+
function useLastViewed() {
|
|
496
|
+
const [map, setMap] = React4.useState(() => read());
|
|
497
|
+
React4.useEffect(() => {
|
|
498
|
+
listeners.add(setMap);
|
|
499
|
+
return () => {
|
|
500
|
+
listeners.delete(setMap);
|
|
501
|
+
};
|
|
502
|
+
}, []);
|
|
503
|
+
return map;
|
|
504
|
+
}
|
|
505
|
+
var ANALYTICS_BASE = "/analytics";
|
|
506
|
+
var REPORTS_BASE = `${ANALYTICS_BASE}/reports`;
|
|
507
|
+
var ConfigContext = React4__default.createContext(null);
|
|
508
|
+
function useAnalyticsConfig() {
|
|
509
|
+
const ctx = React4__default.useContext(ConfigContext);
|
|
510
|
+
if (!ctx) throw new Error("useAnalyticsConfig must be used inside <AnalyticsSurface>");
|
|
511
|
+
return ctx;
|
|
512
|
+
}
|
|
513
|
+
function useAnalyticsCards() {
|
|
514
|
+
const runtime = usePluginRuntime();
|
|
515
|
+
const { cards, createdBy } = useAnalyticsConfig();
|
|
516
|
+
return React4__default.useMemo(() => {
|
|
517
|
+
const byId = /* @__PURE__ */ new Map();
|
|
518
|
+
for (const card of getAnalyticsCards(runtime)) byId.set(card.id, card);
|
|
519
|
+
for (const card of cards) byId.set(card.id, card);
|
|
520
|
+
return [...byId.values()].map((c) => c.createdBy ? c : { ...c, createdBy });
|
|
521
|
+
}, [runtime, cards, createdBy]);
|
|
522
|
+
}
|
|
523
|
+
function useOverviewCards() {
|
|
524
|
+
const all = useAnalyticsCards();
|
|
525
|
+
return React4__default.useMemo(
|
|
526
|
+
() => all.filter((c) => c.dashboard && c.dashboard.defaultVisible !== false).sort((a, b) => (a.dashboard?.order ?? 0) - (b.dashboard?.order ?? 0)),
|
|
527
|
+
[all]
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
function cardColumns(card) {
|
|
531
|
+
if (card.columns?.length) return card.columns;
|
|
532
|
+
return [
|
|
533
|
+
...(card.query.dimensions ?? []).map((d) => ({
|
|
534
|
+
key: d.key,
|
|
535
|
+
label: d.label,
|
|
536
|
+
type: d.format ?? (d.grain ? "date" : "text"),
|
|
537
|
+
align: "left"
|
|
538
|
+
})),
|
|
539
|
+
...(card.query.measures ?? []).map((m) => ({
|
|
540
|
+
key: m.key,
|
|
541
|
+
label: m.label,
|
|
542
|
+
type: m.format ?? "number",
|
|
543
|
+
align: "right",
|
|
544
|
+
sortable: true
|
|
545
|
+
}))
|
|
546
|
+
];
|
|
547
|
+
}
|
|
548
|
+
function navigateAnalytics(path) {
|
|
549
|
+
hashRouterAdapter().navigate(path);
|
|
550
|
+
}
|
|
551
|
+
function AnalyticsSurface({ config, provider, children }) {
|
|
552
|
+
const openCard = React4__default.useCallback((card) => {
|
|
553
|
+
markViewed(card.id);
|
|
554
|
+
navigateAnalytics(`${REPORTS_BASE}/${encodeURIComponent(card.id)}`);
|
|
555
|
+
}, []);
|
|
556
|
+
return /* @__PURE__ */ jsx(ConfigContext.Provider, { value: config, children: /* @__PURE__ */ jsx(
|
|
557
|
+
AnalyticsContextProvider,
|
|
558
|
+
{
|
|
559
|
+
provider,
|
|
560
|
+
currency: config.currency,
|
|
561
|
+
defaultPreset: config.defaultRange,
|
|
562
|
+
defaultCompare: config.defaultCompare,
|
|
563
|
+
defaultPageSize: config.defaultPageSize,
|
|
564
|
+
onOpenCard: openCard,
|
|
565
|
+
createdBy: config.createdBy,
|
|
566
|
+
children
|
|
567
|
+
}
|
|
568
|
+
) });
|
|
569
|
+
}
|
|
570
|
+
function AnalyticsOverview() {
|
|
571
|
+
const { labels, showHeader } = useAnalyticsConfig();
|
|
572
|
+
const cards = useOverviewCards();
|
|
573
|
+
const filters = React4__default.useMemo(() => collectFilters(cards), [cards]);
|
|
574
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
575
|
+
showHeader ? /* @__PURE__ */ jsxs("div", { children: [
|
|
576
|
+
/* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold tracking-tight", children: labels.analytics }),
|
|
577
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: labels.overviewSubtitle })
|
|
578
|
+
] }) : null,
|
|
579
|
+
/* @__PURE__ */ jsx(AnalyticsControls, { filters, className: "sticky top-0 z-10 -mx-1 bg-background/95 px-1 py-2 backdrop-blur" }),
|
|
580
|
+
cards.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center rounded-xl border border-dashed py-16 text-center", children: [
|
|
581
|
+
/* @__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" }) }),
|
|
582
|
+
/* @__PURE__ */ jsx("p", { className: "font-medium", children: labels.emptyOverviewTitle }),
|
|
583
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 max-w-sm text-sm text-muted-foreground", children: labels.emptyOverviewDescription })
|
|
584
|
+
] }) : /* @__PURE__ */ jsx(AnalyticsGrid, { cards })
|
|
585
|
+
] });
|
|
586
|
+
}
|
|
587
|
+
function formatViewed(iso, locale, never) {
|
|
588
|
+
if (!iso) return never;
|
|
589
|
+
const d = new Date(iso);
|
|
590
|
+
if (Number.isNaN(d.getTime())) return never;
|
|
591
|
+
return d.toLocaleDateString(locale, { day: "numeric", month: "short", year: "numeric" });
|
|
592
|
+
}
|
|
593
|
+
function ReportsListPage() {
|
|
594
|
+
const { labels, currency, showHeader } = useAnalyticsConfig();
|
|
595
|
+
const cards = useAnalyticsCards();
|
|
596
|
+
const lastViewed = useLastViewed();
|
|
597
|
+
const [search, setSearch] = React4__default.useState("");
|
|
598
|
+
const [category, setCategory] = React4__default.useState();
|
|
599
|
+
const [author, setAuthor] = React4__default.useState();
|
|
600
|
+
const categoryOptions = React4__default.useMemo(
|
|
601
|
+
() => [...new Set(cards.map((c) => c.category))].sort((a, b) => a.localeCompare(b)).map((c) => ({ value: c, label: c })),
|
|
602
|
+
[cards]
|
|
603
|
+
);
|
|
604
|
+
const authorOptions = React4__default.useMemo(
|
|
605
|
+
() => [...new Set(cards.map((c) => c.createdBy ?? "\u2014"))].sort((a, b) => a.localeCompare(b)).map((a) => ({ value: a, label: a })),
|
|
606
|
+
[cards]
|
|
607
|
+
);
|
|
608
|
+
const rows = React4__default.useMemo(() => {
|
|
609
|
+
const q = search.trim().toLowerCase();
|
|
610
|
+
return cards.filter((card) => {
|
|
611
|
+
if (category && card.category !== category) return false;
|
|
612
|
+
if (author && (card.createdBy ?? "\u2014") !== author) return false;
|
|
613
|
+
if (!q) return true;
|
|
614
|
+
return card.name.toLowerCase().includes(q) || card.description?.toLowerCase().includes(q) || card.category.toLowerCase().includes(q);
|
|
615
|
+
});
|
|
616
|
+
}, [cards, search, category, author]);
|
|
617
|
+
const columns = React4__default.useMemo(() => [
|
|
618
|
+
{
|
|
619
|
+
id: "name",
|
|
620
|
+
accessorFn: (card) => card.name,
|
|
621
|
+
header: labels.name,
|
|
622
|
+
cell: ({ row }) => /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
623
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: row.original.name }),
|
|
624
|
+
row.original.available === false ? /* @__PURE__ */ jsx("span", { className: "ml-2 rounded-full border px-1.5 py-0.5 text-[10px] text-muted-foreground", children: labels.unavailableTitle }) : null,
|
|
625
|
+
row.original.description ? /* @__PURE__ */ jsx("p", { className: "mt-0.5 line-clamp-1 text-muted-foreground sm:hidden", children: row.original.description }) : null
|
|
626
|
+
] })
|
|
627
|
+
},
|
|
628
|
+
{
|
|
629
|
+
id: "category",
|
|
630
|
+
accessorFn: (card) => card.category,
|
|
631
|
+
header: labels.category,
|
|
632
|
+
cell: ({ getValue }) => /* @__PURE__ */ jsx("span", { className: "rounded-md bg-muted px-2 py-0.5", children: String(getValue() ?? "") })
|
|
633
|
+
},
|
|
634
|
+
{
|
|
635
|
+
id: "lastViewed",
|
|
636
|
+
// Sorts on the timestamp, renders the date. Never-opened reports carry
|
|
637
|
+
// undefined so they sink to the bottom in BOTH directions rather than
|
|
638
|
+
// topping "most recent" as an epoch-zero date.
|
|
639
|
+
accessorFn: (card) => {
|
|
640
|
+
const iso = lastViewed[card.id];
|
|
641
|
+
return iso ? new Date(iso).getTime() : void 0;
|
|
642
|
+
},
|
|
643
|
+
sortUndefined: 1,
|
|
644
|
+
header: labels.lastViewed,
|
|
645
|
+
cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: formatViewed(lastViewed[row.original.id], currency.locale, labels.never) })
|
|
646
|
+
},
|
|
647
|
+
{
|
|
648
|
+
id: "createdBy",
|
|
649
|
+
accessorFn: (card) => card.createdBy ?? "\u2014",
|
|
650
|
+
header: labels.createdBy,
|
|
651
|
+
cell: ({ getValue }) => /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5 text-muted-foreground", children: [
|
|
652
|
+
/* @__PURE__ */ jsx(User, { className: "h-3 w-3" }),
|
|
653
|
+
String(getValue() ?? "\u2014")
|
|
654
|
+
] })
|
|
655
|
+
}
|
|
656
|
+
], [labels, currency.locale, lastViewed]);
|
|
657
|
+
const open = (card) => {
|
|
658
|
+
markViewed(card.id);
|
|
659
|
+
navigateAnalytics(`${REPORTS_BASE}/${encodeURIComponent(card.id)}`);
|
|
660
|
+
};
|
|
661
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
662
|
+
showHeader ? /* @__PURE__ */ jsxs("div", { children: [
|
|
663
|
+
/* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold tracking-tight", children: labels.reports }),
|
|
664
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: labels.reportsSubtitle })
|
|
665
|
+
] }) : null,
|
|
666
|
+
/* @__PURE__ */ jsxs(Card, { className: "overflow-hidden", children: [
|
|
667
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 border-b border-border px-3 py-2", children: [
|
|
668
|
+
/* @__PURE__ */ jsx("div", { className: "min-w-[10rem] flex-1", children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
669
|
+
/* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" }),
|
|
670
|
+
/* @__PURE__ */ jsx(
|
|
671
|
+
"input",
|
|
672
|
+
{
|
|
673
|
+
type: "text",
|
|
674
|
+
value: search,
|
|
675
|
+
onChange: (e) => setSearch(e.target.value),
|
|
676
|
+
placeholder: labels.searchReports,
|
|
677
|
+
className: "w-full bg-transparent py-1.5 pl-8 pr-3 text-sm placeholder:text-muted-foreground focus:outline-none"
|
|
678
|
+
}
|
|
679
|
+
)
|
|
680
|
+
] }) }),
|
|
681
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 flex-wrap items-center gap-1.5", children: [
|
|
682
|
+
/* @__PURE__ */ jsx(
|
|
683
|
+
FilterChip,
|
|
684
|
+
{
|
|
685
|
+
label: labels.category,
|
|
686
|
+
allLabel: labels.allCategories,
|
|
687
|
+
options: categoryOptions,
|
|
688
|
+
value: category,
|
|
689
|
+
onChange: setCategory
|
|
690
|
+
}
|
|
691
|
+
),
|
|
692
|
+
/* @__PURE__ */ jsx(
|
|
693
|
+
FilterChip,
|
|
694
|
+
{
|
|
695
|
+
label: labels.createdBy,
|
|
696
|
+
allLabel: labels.allCreatedBy,
|
|
697
|
+
options: authorOptions,
|
|
698
|
+
value: author,
|
|
699
|
+
onChange: setAuthor
|
|
700
|
+
}
|
|
701
|
+
)
|
|
702
|
+
] })
|
|
703
|
+
] }),
|
|
287
704
|
/* @__PURE__ */ jsx(
|
|
288
|
-
|
|
705
|
+
DataTable,
|
|
289
706
|
{
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
] }, size))
|
|
707
|
+
columns,
|
|
708
|
+
data: rows,
|
|
709
|
+
variant: "bare",
|
|
710
|
+
compact: true,
|
|
711
|
+
onRowClick: open,
|
|
712
|
+
emptyMessage: labels.noResults
|
|
297
713
|
}
|
|
298
714
|
),
|
|
299
|
-
/* @__PURE__ */ jsxs("div", { className: "
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
variant: "outline",
|
|
304
|
-
size: "sm",
|
|
305
|
-
disabled: page <= 1,
|
|
306
|
-
onClick: () => onPageChange(page - 1),
|
|
307
|
-
children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" })
|
|
308
|
-
}
|
|
309
|
-
),
|
|
310
|
-
/* @__PURE__ */ jsxs("span", { className: "text-sm px-2", children: [
|
|
311
|
-
page,
|
|
312
|
-
" / ",
|
|
313
|
-
totalPages
|
|
314
|
-
] }),
|
|
315
|
-
/* @__PURE__ */ jsx(
|
|
316
|
-
Button,
|
|
317
|
-
{
|
|
318
|
-
variant: "outline",
|
|
319
|
-
size: "sm",
|
|
320
|
-
disabled: page >= totalPages,
|
|
321
|
-
onClick: () => onPageChange(page + 1),
|
|
322
|
-
children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" })
|
|
323
|
-
}
|
|
324
|
-
)
|
|
715
|
+
/* @__PURE__ */ jsxs("div", { className: "border-t border-border px-3 py-2 text-xs text-muted-foreground", children: [
|
|
716
|
+
rows.length,
|
|
717
|
+
" ",
|
|
718
|
+
labels.results
|
|
325
719
|
] })
|
|
326
720
|
] })
|
|
327
721
|
] });
|
|
328
722
|
}
|
|
329
|
-
function ReportSummaryRow({ columns, data, currency }) {
|
|
330
|
-
const visibleCols = columns.filter((c) => c.visible !== false);
|
|
331
|
-
const hasAggregates = visibleCols.some((c) => c.aggregate);
|
|
332
|
-
if (!hasAggregates || data.length === 0) return null;
|
|
333
|
-
return /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-muted/30 overflow-hidden", children: /* @__PURE__ */ jsx("table", { className: "w-full text-xs", children: /* @__PURE__ */ jsx("tbody", { children: /* @__PURE__ */ jsx("tr", { className: "font-semibold", children: visibleCols.map((col) => {
|
|
334
|
-
const value = computeAggregate(col, data);
|
|
335
|
-
const alignRight = col.align === "right" || col.type === "currency" || col.type === "number";
|
|
336
|
-
return /* @__PURE__ */ jsx(
|
|
337
|
-
"td",
|
|
338
|
-
{
|
|
339
|
-
className: `px-3 py-1.5 ${alignRight ? "text-right" : ""}`,
|
|
340
|
-
children: value != null ? formatValuePlain(value, col, currency) : col.aggregate ? "\u2014" : ""
|
|
341
|
-
},
|
|
342
|
-
col.key
|
|
343
|
-
);
|
|
344
|
-
}) }) }) }) });
|
|
345
|
-
}
|
|
346
|
-
function computeAggregate(col, data) {
|
|
347
|
-
if (!col.aggregate) return null;
|
|
348
|
-
const values = data.map((row) => row[col.key]).filter((v) => v != null && !isNaN(Number(v))).map(Number);
|
|
349
|
-
if (values.length === 0) return null;
|
|
350
|
-
switch (col.aggregate) {
|
|
351
|
-
case "sum":
|
|
352
|
-
return values.reduce((a, b) => a + b, 0);
|
|
353
|
-
case "avg":
|
|
354
|
-
return values.reduce((a, b) => a + b, 0) / values.length;
|
|
355
|
-
case "count":
|
|
356
|
-
return values.length;
|
|
357
|
-
case "min":
|
|
358
|
-
return Math.min(...values);
|
|
359
|
-
case "max":
|
|
360
|
-
return Math.max(...values);
|
|
361
|
-
default:
|
|
362
|
-
return null;
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
723
|
function exportToCsv(columns, data, filename, currency) {
|
|
366
724
|
const visibleCols = columns.filter((c) => c.visible !== false);
|
|
367
725
|
const csvColumns = visibleCols.map((col) => ({
|
|
@@ -376,18 +734,18 @@ function ExportDropdown({ columns, data, filename, currency, title }) {
|
|
|
376
734
|
const [excelAvailable, setExcelAvailable] = useState(false);
|
|
377
735
|
const [pdfAvailable, setPdfAvailable] = useState(false);
|
|
378
736
|
useEffect(() => {
|
|
379
|
-
import('./excel-
|
|
737
|
+
import('./excel-WE73YE2V.js').then((m) => m.isExcelAvailable()).then(setExcelAvailable).catch(() => {
|
|
380
738
|
});
|
|
381
|
-
import('./pdf-
|
|
739
|
+
import('./pdf-E6DXHFXU.js').then((m) => m.isPdfAvailable()).then(setPdfAvailable).catch(() => {
|
|
382
740
|
});
|
|
383
741
|
}, []);
|
|
384
742
|
const handleCsv = () => exportToCsv(columns, data, filename, currency);
|
|
385
743
|
const handleExcel = async () => {
|
|
386
|
-
const { exportToExcel } = await import('./excel-
|
|
744
|
+
const { exportToExcel } = await import('./excel-WE73YE2V.js');
|
|
387
745
|
await exportToExcel(columns, data, filename, currency);
|
|
388
746
|
};
|
|
389
747
|
const handlePdf = async () => {
|
|
390
|
-
const { exportToPdf } = await import('./pdf-
|
|
748
|
+
const { exportToPdf } = await import('./pdf-E6DXHFXU.js');
|
|
391
749
|
await exportToPdf(columns, data, filename, { title, currency });
|
|
392
750
|
};
|
|
393
751
|
return /* @__PURE__ */ jsxs(Dropdown, { children: [
|
|
@@ -402,205 +760,191 @@ function ExportDropdown({ columns, data, filename, currency, title }) {
|
|
|
402
760
|
] })
|
|
403
761
|
] });
|
|
404
762
|
}
|
|
405
|
-
function
|
|
406
|
-
const
|
|
407
|
-
const
|
|
408
|
-
const
|
|
409
|
-
|
|
410
|
-
total,
|
|
411
|
-
|
|
412
|
-
generated,
|
|
413
|
-
error,
|
|
414
|
-
query,
|
|
415
|
-
generate,
|
|
416
|
-
setDateRange,
|
|
417
|
-
setFilter,
|
|
418
|
-
setPage,
|
|
419
|
-
setPageSize
|
|
420
|
-
} = useReportData(report, provider, config.defaultPageSize);
|
|
421
|
-
const isAvailable = report.available !== false;
|
|
422
|
-
const tanstackColumns = useMemo(
|
|
423
|
-
() => reportColumnsToTanstack(report.columns, config.currency.code),
|
|
424
|
-
[report.columns, config.currency.code]
|
|
425
|
-
);
|
|
426
|
-
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
427
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-sm no-print", children: [
|
|
428
|
-
/* @__PURE__ */ jsxs(
|
|
429
|
-
"button",
|
|
430
|
-
{
|
|
431
|
-
onClick: onBack,
|
|
432
|
-
className: "flex items-center gap-1 text-muted-foreground hover:text-foreground transition-colors",
|
|
433
|
-
children: [
|
|
434
|
-
/* @__PURE__ */ jsx(ArrowLeft, { className: "h-3.5 w-3.5" }),
|
|
435
|
-
t2("reports.pageTitle")
|
|
436
|
-
]
|
|
437
|
-
}
|
|
438
|
-
),
|
|
439
|
-
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "/" }),
|
|
440
|
-
/* @__PURE__ */ jsx("span", { className: "font-medium", children: report.name })
|
|
441
|
-
] }),
|
|
442
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
443
|
-
/* @__PURE__ */ jsx("h2", { className: "text-lg font-bold", children: report.name }),
|
|
444
|
-
report.description && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-0.5", children: report.description })
|
|
445
|
-
] }),
|
|
446
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-end gap-3 no-print", children: [
|
|
763
|
+
function ReportPagination({ page: page2, pageSize, total, onPageChange, onPageSizeChange }) {
|
|
764
|
+
const totalPages = Math.max(1, Math.ceil(total / pageSize));
|
|
765
|
+
const from = (page2 - 1) * pageSize + 1;
|
|
766
|
+
const to = Math.min(page2 * pageSize, total);
|
|
767
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-t px-4 py-3", children: [
|
|
768
|
+
/* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: total > 0 ? `${from}\u2013${to} of ${total}` : "No results" }),
|
|
769
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
447
770
|
/* @__PURE__ */ jsx(
|
|
448
|
-
|
|
771
|
+
"select",
|
|
449
772
|
{
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
773
|
+
value: pageSize,
|
|
774
|
+
onChange: (e) => onPageSizeChange(Number(e.target.value)),
|
|
775
|
+
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",
|
|
776
|
+
children: [25, 50, 100].map((size) => /* @__PURE__ */ jsxs("option", { value: size, children: [
|
|
777
|
+
size,
|
|
778
|
+
" / page"
|
|
779
|
+
] }, size))
|
|
455
780
|
}
|
|
456
781
|
),
|
|
457
|
-
/* @__PURE__ */ jsxs(
|
|
458
|
-
loading ? /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 mr-1.5 animate-spin" }) : /* @__PURE__ */ jsx(Play, { className: "h-3.5 w-3.5 mr-1.5" }),
|
|
459
|
-
t2("reports.generate")
|
|
460
|
-
] }),
|
|
461
|
-
generated && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
782
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
462
783
|
/* @__PURE__ */ jsx(
|
|
463
|
-
|
|
784
|
+
Button,
|
|
464
785
|
{
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
786
|
+
variant: "outline",
|
|
787
|
+
size: "sm",
|
|
788
|
+
disabled: page2 <= 1,
|
|
789
|
+
onClick: () => onPageChange(page2 - 1),
|
|
790
|
+
children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" })
|
|
470
791
|
}
|
|
471
792
|
),
|
|
472
|
-
/* @__PURE__ */ jsxs(
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
] })
|
|
477
|
-
] }),
|
|
478
|
-
generated && /* @__PURE__ */ jsx("div", { className: "print-only hidden text-[10px] border-b pb-2 mb-2", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-x-4 gap-y-0.5", children: [
|
|
479
|
-
query.dateRange.from && /* @__PURE__ */ jsxs("span", { children: [
|
|
480
|
-
/* @__PURE__ */ jsxs("strong", { children: [
|
|
481
|
-
t2("reports.filterFrom"),
|
|
482
|
-
":"
|
|
483
|
-
] }),
|
|
484
|
-
" ",
|
|
485
|
-
(/* @__PURE__ */ new Date(query.dateRange.from + "T12:00")).toLocaleDateString()
|
|
486
|
-
] }),
|
|
487
|
-
query.dateRange.to && /* @__PURE__ */ jsxs("span", { children: [
|
|
488
|
-
/* @__PURE__ */ jsxs("strong", { children: [
|
|
489
|
-
t2("reports.filterTo"),
|
|
490
|
-
":"
|
|
491
|
-
] }),
|
|
492
|
-
" ",
|
|
493
|
-
(/* @__PURE__ */ new Date(query.dateRange.to + "T12:00")).toLocaleDateString()
|
|
494
|
-
] }),
|
|
495
|
-
Object.entries(query.filters).map(
|
|
496
|
-
([key, value]) => value ? /* @__PURE__ */ jsxs("span", { children: [
|
|
497
|
-
/* @__PURE__ */ jsxs("strong", { children: [
|
|
498
|
-
key,
|
|
499
|
-
":"
|
|
500
|
-
] }),
|
|
501
|
-
" ",
|
|
502
|
-
String(value)
|
|
503
|
-
] }, key) : null
|
|
504
|
-
),
|
|
505
|
-
/* @__PURE__ */ jsxs("span", { children: [
|
|
506
|
-
/* @__PURE__ */ jsxs("strong", { children: [
|
|
507
|
-
t2("reports.filterResults"),
|
|
508
|
-
":"
|
|
793
|
+
/* @__PURE__ */ jsxs("span", { className: "text-sm px-2", children: [
|
|
794
|
+
page2,
|
|
795
|
+
" / ",
|
|
796
|
+
totalPages
|
|
509
797
|
] }),
|
|
510
|
-
|
|
511
|
-
|
|
798
|
+
/* @__PURE__ */ jsx(
|
|
799
|
+
Button,
|
|
800
|
+
{
|
|
801
|
+
variant: "outline",
|
|
802
|
+
size: "sm",
|
|
803
|
+
disabled: page2 >= totalPages,
|
|
804
|
+
onClick: () => onPageChange(page2 + 1),
|
|
805
|
+
children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" })
|
|
806
|
+
}
|
|
807
|
+
)
|
|
512
808
|
] })
|
|
513
|
-
] }) }),
|
|
514
|
-
error && /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-destructive/50 bg-destructive/10 p-3 text-sm text-destructive", children: error }),
|
|
515
|
-
!generated && !loading && /* @__PURE__ */ jsx("div", { className: "flex flex-col items-center justify-center rounded-xl border border-dashed py-16 text-center", children: isAvailable ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
516
|
-
/* @__PURE__ */ jsx("div", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-muted mb-4", children: /* @__PURE__ */ jsx(SlidersHorizontal, { className: "h-5 w-5 text-muted-foreground" }) }),
|
|
517
|
-
/* @__PURE__ */ jsx("p", { className: "font-medium", children: t2("reports.emptyStateTitle") }),
|
|
518
|
-
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-1 max-w-sm", children: t2("reports.emptyStateDescription") })
|
|
519
|
-
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
520
|
-
/* @__PURE__ */ jsx("div", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-muted mb-4", children: /* @__PURE__ */ jsx(Construction, { className: "h-5 w-5 text-muted-foreground" }) }),
|
|
521
|
-
/* @__PURE__ */ jsx("p", { className: "font-medium", children: t2("reports.unavailableTitle") }),
|
|
522
|
-
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-1 max-w-sm", children: t2("reports.unavailableDescription") })
|
|
523
|
-
] }) }),
|
|
524
|
-
(generated || loading) && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
525
|
-
/* @__PURE__ */ jsx(
|
|
526
|
-
DataTable,
|
|
527
|
-
{
|
|
528
|
-
columns: tanstackColumns,
|
|
529
|
-
data,
|
|
530
|
-
loading,
|
|
531
|
-
skeletonRows: 5,
|
|
532
|
-
emptyMessage: t2("reports.noResults"),
|
|
533
|
-
variant: "card",
|
|
534
|
-
compact: true
|
|
535
|
-
}
|
|
536
|
-
),
|
|
537
|
-
report.showSummary && !loading && /* @__PURE__ */ jsx(
|
|
538
|
-
ReportSummaryRow,
|
|
539
|
-
{
|
|
540
|
-
columns: report.columns,
|
|
541
|
-
data,
|
|
542
|
-
currency: config.currency.code
|
|
543
|
-
}
|
|
544
|
-
),
|
|
545
|
-
!loading && /* @__PURE__ */ jsx("div", { className: "no-print", children: /* @__PURE__ */ jsx(
|
|
546
|
-
ReportPagination,
|
|
547
|
-
{
|
|
548
|
-
page: query.page,
|
|
549
|
-
pageSize: query.pageSize,
|
|
550
|
-
total,
|
|
551
|
-
onPageChange: setPage,
|
|
552
|
-
onPageSizeChange: setPageSize
|
|
553
|
-
}
|
|
554
|
-
) })
|
|
555
809
|
] })
|
|
556
810
|
] });
|
|
557
811
|
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
const
|
|
561
|
-
|
|
562
|
-
|
|
812
|
+
function ViewerBody({ card }) {
|
|
813
|
+
const { labels, currency } = useAnalyticsConfig();
|
|
814
|
+
const { state, setPage, setPageSize } = useAnalyticsContext();
|
|
815
|
+
const { result, loading, error, engineMissing } = useAnalyticsCard(card);
|
|
816
|
+
const columns = React4__default.useMemo(() => cardColumns(card), [card]);
|
|
817
|
+
const tanstackColumns = React4__default.useMemo(
|
|
818
|
+
() => reportColumnsToTanstack(columns, currency.code),
|
|
819
|
+
[columns, currency.code]
|
|
820
|
+
);
|
|
821
|
+
const rows = result?.rows ?? [];
|
|
822
|
+
if (card.available === false) {
|
|
823
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center rounded-xl border border-dashed py-16 text-center", children: [
|
|
824
|
+
/* @__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" }) }),
|
|
825
|
+
/* @__PURE__ */ jsx("p", { className: "font-medium", children: labels.unavailableTitle }),
|
|
826
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 max-w-sm text-sm text-muted-foreground", children: labels.unavailableDescription })
|
|
827
|
+
] });
|
|
563
828
|
}
|
|
564
|
-
|
|
829
|
+
const hasViz = card.viz.type !== "table" || (card.query.measures?.length ?? 0) > 0;
|
|
830
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
831
|
+
hasViz ? /* @__PURE__ */ jsx(
|
|
832
|
+
AnalyticsCard,
|
|
833
|
+
{
|
|
834
|
+
card: { ...card, viz: { ...card.viz, height: card.viz.height ?? 280, limit: void 0 } },
|
|
835
|
+
hideOpen: true
|
|
836
|
+
}
|
|
837
|
+
) : null,
|
|
838
|
+
engineMissing ? /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-muted/40 p-3 text-sm", children: [
|
|
839
|
+
/* @__PURE__ */ jsx("p", { className: "font-medium", children: "Analytics engine not installed" }),
|
|
840
|
+
/* @__PURE__ */ jsxs("p", { className: "mt-0.5 text-muted-foreground", children: [
|
|
841
|
+
"This report aggregates server-side and needs migration 0039 (",
|
|
842
|
+
/* @__PURE__ */ jsx("code", { className: "text-xs", children: "analytics_run" }),
|
|
843
|
+
" + the read models) applied to this project."
|
|
844
|
+
] })
|
|
845
|
+
] }) : error ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-destructive/50 bg-destructive/10 p-3 text-sm text-destructive", children: error }) : null,
|
|
846
|
+
/* @__PURE__ */ jsx(
|
|
847
|
+
DataTable,
|
|
848
|
+
{
|
|
849
|
+
columns: tanstackColumns,
|
|
850
|
+
data: rows,
|
|
851
|
+
loading,
|
|
852
|
+
skeletonRows: 5,
|
|
853
|
+
emptyMessage: labels.noResults,
|
|
854
|
+
variant: "card",
|
|
855
|
+
compact: true
|
|
856
|
+
}
|
|
857
|
+
),
|
|
858
|
+
!loading ? /* @__PURE__ */ jsx("div", { className: "no-print", children: /* @__PURE__ */ jsx(
|
|
859
|
+
ReportPagination,
|
|
860
|
+
{
|
|
861
|
+
page: state.page,
|
|
862
|
+
pageSize: state.pageSize,
|
|
863
|
+
total: result?.total ?? 0,
|
|
864
|
+
onPageChange: setPage,
|
|
865
|
+
onPageSizeChange: setPageSize
|
|
866
|
+
}
|
|
867
|
+
) }) : null
|
|
868
|
+
] });
|
|
565
869
|
}
|
|
566
|
-
function
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
870
|
+
function AnalyticsReportViewer({ cardId }) {
|
|
871
|
+
const { labels } = useAnalyticsConfig();
|
|
872
|
+
const cards = useAnalyticsCards();
|
|
873
|
+
const card = cards.find((c) => c.id === decodeURIComponent(cardId ?? ""));
|
|
874
|
+
React4__default.useEffect(() => {
|
|
875
|
+
if (card) markViewed(card.id);
|
|
876
|
+
}, [card?.id]);
|
|
877
|
+
if (!card) {
|
|
878
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
879
|
+
/* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: () => navigateAnalytics(REPORTS_BASE), children: [
|
|
880
|
+
/* @__PURE__ */ jsx(ArrowLeft, { className: "mr-1.5 h-3.5 w-3.5" }),
|
|
881
|
+
labels.reports
|
|
882
|
+
] }),
|
|
883
|
+
/* @__PURE__ */ jsx("p", { className: "py-12 text-center text-sm text-muted-foreground", children: labels.noResults })
|
|
884
|
+
] });
|
|
885
|
+
}
|
|
886
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
887
|
+
/* @__PURE__ */ jsx(
|
|
888
|
+
SubpageHeader,
|
|
889
|
+
{
|
|
890
|
+
title: card.name,
|
|
891
|
+
parentLabel: labels.reports,
|
|
892
|
+
onBack: () => navigateAnalytics(REPORTS_BASE)
|
|
893
|
+
}
|
|
894
|
+
),
|
|
895
|
+
card.description ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: card.description }) : null,
|
|
896
|
+
/* @__PURE__ */ jsx(
|
|
897
|
+
AnalyticsControls,
|
|
898
|
+
{
|
|
899
|
+
filters: card.query.filters ?? [],
|
|
900
|
+
showCompare: card.compare !== false,
|
|
901
|
+
className: "no-print",
|
|
902
|
+
actions: /* @__PURE__ */ jsx(ViewerActions, { card })
|
|
903
|
+
}
|
|
904
|
+
),
|
|
905
|
+
/* @__PURE__ */ jsx(ViewerBody, { card })
|
|
906
|
+
] });
|
|
907
|
+
}
|
|
908
|
+
function ViewerActions({ card }) {
|
|
909
|
+
const { currency } = useAnalyticsConfig();
|
|
910
|
+
const { result } = useAnalyticsCard(card);
|
|
911
|
+
const columns = React4__default.useMemo(() => cardColumns(card), [card]);
|
|
912
|
+
if (!result?.rows?.length) return null;
|
|
913
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
914
|
+
/* @__PURE__ */ jsx(
|
|
915
|
+
ExportDropdown,
|
|
916
|
+
{
|
|
917
|
+
columns,
|
|
918
|
+
data: result.rows,
|
|
919
|
+
filename: card.id,
|
|
920
|
+
currency: currency.code,
|
|
921
|
+
title: card.name
|
|
922
|
+
}
|
|
923
|
+
),
|
|
924
|
+
/* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", className: "h-8", onClick: () => window.print(), children: [
|
|
925
|
+
/* @__PURE__ */ jsx(Printer, { className: "mr-1.5 h-3.5 w-3.5" }),
|
|
926
|
+
"Print"
|
|
927
|
+
] })
|
|
928
|
+
] });
|
|
929
|
+
}
|
|
930
|
+
function LiveViewPage() {
|
|
931
|
+
const { labels } = useAnalyticsConfig();
|
|
932
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center rounded-xl border border-dashed py-20 text-center", children: [
|
|
933
|
+
/* @__PURE__ */ jsx("div", { className: "mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-muted", children: /* @__PURE__ */ jsx(Radio, { className: "h-5 w-5 text-muted-foreground" }) }),
|
|
934
|
+
/* @__PURE__ */ jsx("p", { className: "font-medium", children: labels.liveView }),
|
|
935
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 max-w-sm text-sm text-muted-foreground", children: labels.liveViewSoon })
|
|
936
|
+
] });
|
|
593
937
|
}
|
|
594
|
-
function
|
|
938
|
+
function camelToSnake2(str) {
|
|
595
939
|
return str.replace(/[A-Z]/g, (c) => "_" + c.toLowerCase());
|
|
596
940
|
}
|
|
597
|
-
function
|
|
941
|
+
function snakeToCamel2(str) {
|
|
598
942
|
return str.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
599
943
|
}
|
|
600
|
-
function
|
|
944
|
+
function mapRow2(row, columnMap) {
|
|
601
945
|
const result = {};
|
|
602
946
|
for (const [key, value] of Object.entries(row)) {
|
|
603
|
-
const camelKey = columnMap?.[key] ??
|
|
947
|
+
const camelKey = columnMap?.[key] ?? snakeToCamel2(key);
|
|
604
948
|
result[camelKey] = value;
|
|
605
949
|
}
|
|
606
950
|
return result;
|
|
@@ -644,7 +988,7 @@ async function fetchViaView(reportDef, query, getClient, resolveTenantId, tenant
|
|
|
644
988
|
}
|
|
645
989
|
for (const [key, value] of Object.entries(query.filters)) {
|
|
646
990
|
if (value == null || value === "") continue;
|
|
647
|
-
const col =
|
|
991
|
+
const col = camelToSnake2(key);
|
|
648
992
|
if (Array.isArray(value)) {
|
|
649
993
|
q = q.in(col, value);
|
|
650
994
|
} else {
|
|
@@ -652,14 +996,14 @@ async function fetchViaView(reportDef, query, getClient, resolveTenantId, tenant
|
|
|
652
996
|
}
|
|
653
997
|
}
|
|
654
998
|
if (query.search) {
|
|
655
|
-
const searchableCols = reportDef.columns.filter((c) => c.type === "text" || c.type === "email").map((c) =>
|
|
999
|
+
const searchableCols = reportDef.columns.filter((c) => c.type === "text" || c.type === "email").map((c) => camelToSnake2(c.key));
|
|
656
1000
|
if (searchableCols.length > 0) {
|
|
657
1001
|
const term = `%${query.search}%`;
|
|
658
1002
|
const orClause = searchableCols.map((col) => `${col}.ilike.${term}`).join(",");
|
|
659
1003
|
q = q.or(orClause);
|
|
660
1004
|
}
|
|
661
1005
|
}
|
|
662
|
-
const sortCol = query.sortBy ?
|
|
1006
|
+
const sortCol = query.sortBy ? camelToSnake2(query.sortBy) : dataSource.defaultSort ? camelToSnake2(dataSource.defaultSort) : dateCol;
|
|
663
1007
|
const ascending = query.sortDir ? query.sortDir === "asc" : dataSource.defaultSortDir ? dataSource.defaultSortDir === "asc" : false;
|
|
664
1008
|
q = q.order(sortCol, { ascending });
|
|
665
1009
|
const from = (query.page - 1) * query.pageSize;
|
|
@@ -668,7 +1012,7 @@ async function fetchViaView(reportDef, query, getClient, resolveTenantId, tenant
|
|
|
668
1012
|
const { data, error, count } = await q;
|
|
669
1013
|
if (error) throw error;
|
|
670
1014
|
return {
|
|
671
|
-
data: (data ?? []).map((row) =>
|
|
1015
|
+
data: (data ?? []).map((row) => mapRow2(row, columnMap)),
|
|
672
1016
|
total: count ?? 0
|
|
673
1017
|
};
|
|
674
1018
|
}
|
|
@@ -684,23 +1028,23 @@ async function fetchViaRpc(reportDef, query, getClient, resolveTenantId, columnM
|
|
|
684
1028
|
};
|
|
685
1029
|
for (const [key, value] of Object.entries(query.filters)) {
|
|
686
1030
|
if (value == null || value === "") continue;
|
|
687
|
-
params[`p_${
|
|
1031
|
+
params[`p_${camelToSnake2(key)}`] = value;
|
|
688
1032
|
}
|
|
689
1033
|
if (query.sortBy) {
|
|
690
|
-
params.p_sort_by =
|
|
1034
|
+
params.p_sort_by = camelToSnake2(query.sortBy);
|
|
691
1035
|
params.p_sort_dir = query.sortDir ?? "desc";
|
|
692
1036
|
}
|
|
693
1037
|
const { data, error } = await getClient().rpc(dataSource.name, params);
|
|
694
1038
|
if (error) throw error;
|
|
695
1039
|
if (data && typeof data === "object" && "data" in data) {
|
|
696
1040
|
return {
|
|
697
|
-
data: (data.data ?? []).map((row) =>
|
|
1041
|
+
data: (data.data ?? []).map((row) => mapRow2(row, columnMap)),
|
|
698
1042
|
total: data.total ?? 0
|
|
699
1043
|
};
|
|
700
1044
|
}
|
|
701
1045
|
const rows = Array.isArray(data) ? data : [];
|
|
702
1046
|
return {
|
|
703
|
-
data: rows.map((row) =>
|
|
1047
|
+
data: rows.map((row) => mapRow2(row, columnMap)),
|
|
704
1048
|
total: rows.length
|
|
705
1049
|
};
|
|
706
1050
|
}
|
|
@@ -714,164 +1058,142 @@ function createMockReportProvider() {
|
|
|
714
1058
|
};
|
|
715
1059
|
}
|
|
716
1060
|
|
|
717
|
-
// src/lib/tenant.ts
|
|
718
|
-
var t;
|
|
719
|
-
function getReportsTenantId() {
|
|
720
|
-
return t;
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
// src/locales/en.ts
|
|
724
|
-
var en = {
|
|
725
|
-
"reports.pageTitle": "Reports",
|
|
726
|
-
"reports.pageSubtitle": "Access complete reports for analysis and decision making",
|
|
727
|
-
"reports.allReports": "All Reports",
|
|
728
|
-
"reports.dateRange": "Date Range",
|
|
729
|
-
"reports.from": "From",
|
|
730
|
-
"reports.to": "To",
|
|
731
|
-
"reports.export": "Export",
|
|
732
|
-
"reports.exportCsv": "Export CSV",
|
|
733
|
-
"reports.exportExcel": "Export Excel",
|
|
734
|
-
"reports.exportPdf": "Export PDF",
|
|
735
|
-
"reports.noResults": "No results found.",
|
|
736
|
-
"reports.backToReports": "Back",
|
|
737
|
-
"reports.reports": "reports",
|
|
738
|
-
"reports.search": "Search reports...",
|
|
739
|
-
"reports.today": "Today",
|
|
740
|
-
"reports.yesterday": "Yesterday",
|
|
741
|
-
"reports.last7Days": "Last 7 days",
|
|
742
|
-
"reports.thisMonth": "This month",
|
|
743
|
-
"reports.lastMonth": "Last month",
|
|
744
|
-
"reports.thisQuarter": "This quarter",
|
|
745
|
-
"reports.custom": "Custom",
|
|
746
|
-
"reports.generate": "Generate",
|
|
747
|
-
"reports.emptyStateTitle": "Ready to generate",
|
|
748
|
-
"reports.emptyStateDescription": "Select a date range and filters, then click Generate to view the report.",
|
|
749
|
-
"reports.unavailableTitle": "Coming soon",
|
|
750
|
-
"reports.unavailableDescription": "This report is not available yet. It will be enabled in a future update."
|
|
751
|
-
};
|
|
752
|
-
|
|
753
|
-
// src/locales/pt-BR.ts
|
|
754
|
-
var ptBR = {
|
|
755
|
-
"reports.pageTitle": "Relat\xF3rios",
|
|
756
|
-
"reports.pageSubtitle": "Acesse relat\xF3rios completos para an\xE1lise e tomada de decis\xE3o",
|
|
757
|
-
"reports.allReports": "Todos os Relat\xF3rios",
|
|
758
|
-
"reports.dateRange": "Per\xEDodo",
|
|
759
|
-
"reports.from": "De",
|
|
760
|
-
"reports.to": "At\xE9",
|
|
761
|
-
"reports.export": "Exportar",
|
|
762
|
-
"reports.exportCsv": "Exportar CSV",
|
|
763
|
-
"reports.exportExcel": "Exportar Excel",
|
|
764
|
-
"reports.exportPdf": "Exportar PDF",
|
|
765
|
-
"reports.noResults": "Nenhum resultado encontrado.",
|
|
766
|
-
"reports.backToReports": "Voltar",
|
|
767
|
-
"reports.reports": "relat\xF3rios",
|
|
768
|
-
"reports.search": "Buscar relat\xF3rios...",
|
|
769
|
-
"reports.today": "Hoje",
|
|
770
|
-
"reports.yesterday": "Ontem",
|
|
771
|
-
"reports.last7Days": "\xDAltimos 7 dias",
|
|
772
|
-
"reports.thisMonth": "Este m\xEAs",
|
|
773
|
-
"reports.lastMonth": "M\xEAs passado",
|
|
774
|
-
"reports.thisQuarter": "Este trimestre",
|
|
775
|
-
"reports.custom": "Personalizado",
|
|
776
|
-
"reports.generate": "Gerar",
|
|
777
|
-
"reports.emptyStateTitle": "Pronto para gerar",
|
|
778
|
-
"reports.emptyStateDescription": "Selecione o per\xEDodo e os filtros desejados, depois clique em Gerar para visualizar o relat\xF3rio.",
|
|
779
|
-
"reports.unavailableTitle": "Em breve",
|
|
780
|
-
"reports.unavailableDescription": "Este relat\xF3rio ainda n\xE3o est\xE1 dispon\xEDvel. Ser\xE1 habilitado em uma atualiza\xE7\xE3o futura."
|
|
781
|
-
};
|
|
782
|
-
|
|
783
|
-
// src/locales/index.ts
|
|
784
|
-
var reportsLocales = {
|
|
785
|
-
en,
|
|
786
|
-
"pt-BR": ptBR
|
|
787
|
-
};
|
|
788
|
-
|
|
789
1061
|
// src/index.ts
|
|
790
1062
|
var DEFAULT_LABELS = {
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
1063
|
+
analytics: "Analytics",
|
|
1064
|
+
reports: "Reports",
|
|
1065
|
+
liveView: "Live View",
|
|
1066
|
+
overviewSubtitle: "Key metrics for your business",
|
|
1067
|
+
reportsSubtitle: "Every report available to you",
|
|
1068
|
+
searchReports: "Search reports",
|
|
1069
|
+
category: "Category",
|
|
1070
|
+
createdBy: "Created by",
|
|
1071
|
+
allCategories: "All categories",
|
|
1072
|
+
allCreatedBy: "All authors",
|
|
1073
|
+
lastViewed: "Last viewed",
|
|
1074
|
+
name: "Name",
|
|
801
1075
|
noResults: "No results found.",
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
today: "Today",
|
|
806
|
-
yesterday: "Yesterday",
|
|
807
|
-
last7Days: "Last 7 days",
|
|
808
|
-
thisMonth: "This month",
|
|
809
|
-
lastMonth: "Last month",
|
|
810
|
-
thisQuarter: "This quarter",
|
|
811
|
-
custom: "Custom",
|
|
812
|
-
generate: "Generate",
|
|
1076
|
+
never: "\u2014",
|
|
1077
|
+
back: "Back",
|
|
1078
|
+
export: "Export",
|
|
813
1079
|
print: "Print",
|
|
814
|
-
filterFrom: "From",
|
|
815
|
-
filterTo: "To",
|
|
816
|
-
filterResults: "Results",
|
|
817
|
-
emptyStateTitle: "Ready to generate",
|
|
818
|
-
emptyStateDescription: "Select a date range and filters, then click Generate to view the report.",
|
|
819
1080
|
unavailableTitle: "Coming soon",
|
|
820
|
-
unavailableDescription: "This report is not
|
|
1081
|
+
unavailableDescription: "This report is not connected to data yet. It will be enabled in a future update.",
|
|
1082
|
+
emptyOverviewTitle: "No metrics yet",
|
|
1083
|
+
emptyOverviewDescription: "Enable a plugin that contributes analytics cards, or add them to this app\u2019s pack.",
|
|
1084
|
+
liveViewSoon: "Real-time visitor and checkout activity will appear here once storefront events are being collected.",
|
|
1085
|
+
results: "reports"
|
|
821
1086
|
};
|
|
822
1087
|
var DEFAULT_CURRENCY = { code: "BRL", locale: "pt-BR", symbol: "R$" };
|
|
823
1088
|
function resolveConfig(options) {
|
|
1089
|
+
const labels = { ...DEFAULT_LABELS, ...options.labels };
|
|
1090
|
+
const createdBy = options.createdBy ?? "Fayz";
|
|
824
1091
|
return {
|
|
825
|
-
labels
|
|
1092
|
+
labels,
|
|
826
1093
|
currency: { ...DEFAULT_CURRENCY, ...options.currency },
|
|
827
|
-
|
|
1094
|
+
createdBy,
|
|
1095
|
+
cards: [
|
|
1096
|
+
...options.cards ?? [],
|
|
1097
|
+
...reportDefsToCards(options.reports ?? [], createdBy)
|
|
1098
|
+
],
|
|
828
1099
|
defaultPageSize: options.defaultPageSize ?? 50,
|
|
829
|
-
showHeader: options.showHeader
|
|
1100
|
+
showHeader: options.showHeader === true,
|
|
1101
|
+
defaultRange: options.defaultRange ?? "last_30_days",
|
|
1102
|
+
defaultCompare: options.defaultCompare ?? "previous_period"
|
|
830
1103
|
};
|
|
831
1104
|
}
|
|
832
|
-
function
|
|
1105
|
+
function createAnalyticsPlugin(options = {}) {
|
|
833
1106
|
const config = resolveConfig(options);
|
|
834
1107
|
registerTranslations(reportsLocales);
|
|
835
1108
|
const provider = options.dataProvider ?? createSafeDataProvider(
|
|
836
|
-
() =>
|
|
837
|
-
() =>
|
|
1109
|
+
() => createSupabaseAnalyticsProvider({ tenantId: () => getReportsTenantId() }),
|
|
1110
|
+
() => createMockAnalyticsProvider()
|
|
1111
|
+
);
|
|
1112
|
+
const page2 = (Body) => (props) => React4__default.createElement(
|
|
1113
|
+
AnalyticsSurface,
|
|
1114
|
+
{ config, provider },
|
|
1115
|
+
React4__default.createElement(Body, props)
|
|
838
1116
|
);
|
|
839
|
-
const
|
|
1117
|
+
const READ = { feature: "reports", action: "read" };
|
|
1118
|
+
const position = options.navPosition ?? 9;
|
|
1119
|
+
const section = options.navSection ?? "main";
|
|
1120
|
+
const nav = [
|
|
1121
|
+
{
|
|
1122
|
+
section,
|
|
1123
|
+
position,
|
|
1124
|
+
label: config.labels.analytics,
|
|
1125
|
+
route: ANALYTICS_BASE,
|
|
1126
|
+
icon: "BarChart3",
|
|
1127
|
+
permission: READ
|
|
1128
|
+
},
|
|
1129
|
+
{
|
|
1130
|
+
section,
|
|
1131
|
+
position: position + 0.1,
|
|
1132
|
+
label: config.labels.reports,
|
|
1133
|
+
route: REPORTS_BASE,
|
|
1134
|
+
icon: "FileText",
|
|
1135
|
+
permission: READ
|
|
1136
|
+
},
|
|
1137
|
+
...options.showLiveView ? [{
|
|
1138
|
+
section,
|
|
1139
|
+
position: position + 0.2,
|
|
1140
|
+
label: config.labels.liveView,
|
|
1141
|
+
route: `${ANALYTICS_BASE}/live`,
|
|
1142
|
+
icon: "Radio",
|
|
1143
|
+
permission: READ
|
|
1144
|
+
}] : []
|
|
1145
|
+
];
|
|
840
1146
|
return {
|
|
841
1147
|
id: "reports",
|
|
842
|
-
name: config.labels.
|
|
1148
|
+
name: config.labels.analytics,
|
|
843
1149
|
icon: "BarChart3",
|
|
844
|
-
version: "
|
|
1150
|
+
version: "2.0.0",
|
|
845
1151
|
scope: options.scope ?? "universal",
|
|
846
1152
|
verticalId: options.verticalId,
|
|
847
1153
|
defaultEnabled: true,
|
|
848
1154
|
dependencies: [],
|
|
849
1155
|
declaredFeatures: [
|
|
850
|
-
{ id: "reports", label: config.labels.
|
|
851
|
-
],
|
|
852
|
-
navigation: [
|
|
853
|
-
{
|
|
854
|
-
section: options.navSection ?? "main",
|
|
855
|
-
position: options.navPosition ?? 9,
|
|
856
|
-
label: config.labels.pageTitle,
|
|
857
|
-
route: "/reports",
|
|
858
|
-
icon: "BarChart3",
|
|
859
|
-
permission: { feature: "reports", action: "read" }
|
|
860
|
-
}
|
|
1156
|
+
{ id: "reports", label: config.labels.analytics, group: config.labels.analytics }
|
|
861
1157
|
],
|
|
1158
|
+
navigation: nav,
|
|
862
1159
|
routes: [
|
|
863
|
-
{
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
1160
|
+
{ path: ANALYTICS_BASE, component: page2(AnalyticsOverview), permission: READ },
|
|
1161
|
+
{ path: REPORTS_BASE, component: page2(ReportsListPage), permission: READ },
|
|
1162
|
+
// Registered before the list would ever shadow it: matchRoute scores
|
|
1163
|
+
// static segments above params, so /analytics/reports still wins its own
|
|
1164
|
+
// exact path and only /analytics/reports/<id> lands here.
|
|
1165
|
+
// No `parentRoute` here on purpose: the viewer's title depends on DATA
|
|
1166
|
+
// (the card's name), so it claims the shell bar itself via PageHeader.
|
|
1167
|
+
// Declaring both gave the page two stacked back-crumbs saying the same
|
|
1168
|
+
// thing, one of them generic.
|
|
1169
|
+
{ path: `${REPORTS_BASE}/:cardId`, component: page2(AnalyticsReportViewer), permission: READ },
|
|
1170
|
+
{ path: `${ANALYTICS_BASE}/live`, component: page2(LiveViewPage), permission: READ },
|
|
1171
|
+
// The old top-level route kept as a redirect target: bookmarks, saved
|
|
1172
|
+
// links and the five apps still pointing at /reports must not 404.
|
|
1173
|
+
{ path: "/reports", component: page2(ReportsListPage), permission: READ }
|
|
868
1174
|
],
|
|
869
1175
|
widgets: [],
|
|
870
1176
|
registries: [],
|
|
871
1177
|
locales: reportsLocales
|
|
872
1178
|
};
|
|
873
1179
|
}
|
|
1180
|
+
function createReportsPlugin(options) {
|
|
1181
|
+
return createAnalyticsPlugin({
|
|
1182
|
+
reports: options.reports,
|
|
1183
|
+
labels: {
|
|
1184
|
+
...options.labels?.pageTitle ? { analytics: options.labels.pageTitle } : {},
|
|
1185
|
+
...options.labels?.pageSubtitle ? { overviewSubtitle: options.labels.pageSubtitle } : {}
|
|
1186
|
+
},
|
|
1187
|
+
currency: options.currency,
|
|
1188
|
+
navPosition: options.navPosition,
|
|
1189
|
+
navSection: options.navSection,
|
|
1190
|
+
scope: options.scope,
|
|
1191
|
+
verticalId: options.verticalId,
|
|
1192
|
+
defaultPageSize: options.defaultPageSize,
|
|
1193
|
+
showHeader: options.showHeader
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
874
1196
|
|
|
875
|
-
export { createMockReportProvider, createReportsPlugin, createSupabaseReportProvider };
|
|
1197
|
+
export { ANALYTICS_BASE, AnalyticsOverview, AnalyticsReportViewer, AnalyticsSurface, REPORTS_BASE, ReportsListPage, aggregateRows, cardColumns, createAnalyticsPlugin, createMockAnalyticsProvider, createMockReportProvider, createReportsPlugin, createSupabaseAnalyticsProvider, createSupabaseReportProvider, markViewed, reportDefToCard, reportDefsToCards, useAnalyticsCards, useAnalyticsConfig, useLastViewed, useOverviewCards };
|
|
876
1198
|
//# sourceMappingURL=index.js.map
|
|
877
1199
|
//# sourceMappingURL=index.js.map
|