@fayz-ai/plugin-reports 0.9.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/data/analytics-mock.d.ts.map +1 -1
- package/dist/data/analytics-supabase.d.ts.map +1 -1
- package/dist/index.d.ts +12 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +344 -113
- package/dist/index.js.map +1 -1
- package/dist/lib/tenant.d.ts +1 -0
- package/dist/lib/tenant.d.ts.map +1 -1
- package/dist/locales/en.d.ts.map +1 -1
- package/dist/locales/pt-BR.d.ts.map +1 -1
- package/dist/views/AnalyticsModule.d.ts +10 -0
- package/dist/views/AnalyticsModule.d.ts.map +1 -0
- package/dist/views/AnalyticsOverview.d.ts.map +1 -1
- package/dist/views/AnalyticsReportViewer.d.ts.map +1 -1
- package/dist/views/AnalyticsSurface.d.ts +22 -0
- package/dist/views/AnalyticsSurface.d.ts.map +1 -1
- package/package.json +4 -4
- package/dist/components/ReportCard.d.ts +0 -9
- package/dist/components/ReportCard.d.ts.map +0 -1
- package/dist/components/ReportFilterBar.d.ts +0 -12
- package/dist/components/ReportFilterBar.d.ts.map +0 -1
- package/dist/components/ReportSummaryRow.d.ts +0 -10
- package/dist/components/ReportSummaryRow.d.ts.map +0 -1
- package/dist/context.d.ts +0 -16
- package/dist/context.d.ts.map +0 -1
- package/dist/hooks/useReportData.d.ts +0 -19
- package/dist/hooks/useReportData.d.ts.map +0 -1
- package/dist/views/ReportHub.d.ts +0 -8
- package/dist/views/ReportHub.d.ts.map +0 -1
- package/dist/views/ReportViewer.d.ts +0 -9
- package/dist/views/ReportViewer.d.ts.map +0 -1
- package/dist/views/ReportsPage.d.ts +0 -8
- package/dist/views/ReportsPage.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { reportColumnsToTanstack, formatValuePlain } from './chunk-2ZFN6ECM.js';
|
|
2
|
-
import * as
|
|
3
|
-
import
|
|
4
|
-
import { usePluginRuntime, getAnalyticsCards, registerTranslations, createSafeDataProvider, getSupabaseClientOptional, hashRouterAdapter,
|
|
5
|
-
import {
|
|
2
|
+
import * as React5 from 'react';
|
|
3
|
+
import React5__default, { useState, useEffect } from 'react';
|
|
4
|
+
import { usePluginRuntime, getAnalyticsCards, useTranslation, registerTranslations, createSafeDataProvider, getSupabaseClientOptional, hashRouterAdapter, exportCSV } from '@fayz-ai/core';
|
|
5
|
+
import { useOrganizationStore, useShellRailAvailable, useContextualNav, AssistantInvite } from '@fayz-ai/admin';
|
|
6
|
+
import { AnalyticsContextProvider, useModuleLayout, ModulePage, collectFilters, AnalyticsControls, AnalyticsGrid, Card, FilterChip, DataTable, Button, SubpageHeader, useAnalyticsCard, useAnalyticsContext, AnalyticsCard, Dropdown, DropdownTrigger, DropdownContent, DropdownItem } from '@fayz-ai/ui';
|
|
6
7
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
7
8
|
import { BarChart3, User, Search, ArrowLeft, Printer, Construction, Download, Radio, ChevronLeft, ChevronRight } from 'lucide-react';
|
|
8
9
|
|
|
@@ -59,10 +60,10 @@ function matchesFilters(row, filters) {
|
|
|
59
60
|
function inRange(row, dateColumn, from, to) {
|
|
60
61
|
const raw = row[dateColumn];
|
|
61
62
|
if (raw == null) return true;
|
|
62
|
-
const
|
|
63
|
-
if (Number.isNaN(
|
|
64
|
-
if (from &&
|
|
65
|
-
if (to &&
|
|
63
|
+
const t = new Date(String(raw)).getTime();
|
|
64
|
+
if (Number.isNaN(t)) return true;
|
|
65
|
+
if (from && t < (/* @__PURE__ */ new Date(`${from}T00:00:00`)).getTime()) return false;
|
|
66
|
+
if (to && t > (/* @__PURE__ */ new Date(`${to}T23:59:59.999`)).getTime()) return false;
|
|
66
67
|
return true;
|
|
67
68
|
}
|
|
68
69
|
function applyMeasure(rows, agg, column) {
|
|
@@ -204,6 +205,48 @@ async function selectFromView(card, state, db, tenantId, reserved) {
|
|
|
204
205
|
total: count ?? 0
|
|
205
206
|
};
|
|
206
207
|
}
|
|
208
|
+
function isAggregateCard(card) {
|
|
209
|
+
const { source } = card.query;
|
|
210
|
+
if (source.kind !== "view" || !source.name) return false;
|
|
211
|
+
return (card.query.dimensions?.length ?? 0) > 0 || (card.query.measures?.length ?? 0) > 0;
|
|
212
|
+
}
|
|
213
|
+
function runArgs(card, state, tenantId) {
|
|
214
|
+
const size = state.pageSize || 50;
|
|
215
|
+
const snapshot = card.query.dateColumn === null;
|
|
216
|
+
return {
|
|
217
|
+
source: card.query.source.name,
|
|
218
|
+
tenant_id: tenantId ?? null,
|
|
219
|
+
from: !snapshot && state.dateRange.from ? `${state.dateRange.from}T00:00:00` : null,
|
|
220
|
+
// Inclusive end-of-day: the range picker speaks in days, the column is a
|
|
221
|
+
// timestamp, and closing at midnight loses everything sold that day.
|
|
222
|
+
to: !snapshot && state.dateRange.to ? `${state.dateRange.to}T23:59:59.999` : null,
|
|
223
|
+
dimensions: (card.query.dimensions ?? []).map((d) => ({
|
|
224
|
+
key: d.key,
|
|
225
|
+
...d.grain && d.grain !== "auto" ? { grain: d.grain } : {}
|
|
226
|
+
})),
|
|
227
|
+
measures: (card.query.measures ?? []).filter((m) => m.agg !== "ratio").map((m) => ({ key: m.key, agg: m.agg, ...m.column ? { column: m.column } : {} })),
|
|
228
|
+
filters: state.filters ?? {},
|
|
229
|
+
search: state.search ?? null,
|
|
230
|
+
search_columns: searchColumns(card) ?? null,
|
|
231
|
+
sort: state.sortBy ?? card.query.defaultSort ?? null,
|
|
232
|
+
dir: state.sortDir ?? card.query.defaultSortDir ?? "desc",
|
|
233
|
+
limit: card.query.limit ?? size,
|
|
234
|
+
offset: card.query.limit ? 0 : ((state.page || 1) - 1) * size
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function toResult(data, card, reserved) {
|
|
238
|
+
const rows = data?.rows ?? [];
|
|
239
|
+
return {
|
|
240
|
+
rows: rows.map((r) => mapRow(r, reserved, card.query.source.columnMap)),
|
|
241
|
+
total: data?.total ?? rows.length,
|
|
242
|
+
summary: data?.summary ?? void 0
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function batchUnavailable(err) {
|
|
246
|
+
const code = typeof err?.code === "string" ? err.code : "";
|
|
247
|
+
if (["42883", "PGRST202"].includes(code)) return true;
|
|
248
|
+
return typeof err?.message === "string" && err.message.includes("analytics_run_batch");
|
|
249
|
+
}
|
|
207
250
|
function createSupabaseAnalyticsProvider(config) {
|
|
208
251
|
const resolveTenantId = () => (typeof config.tenantId === "function" ? config.tenantId() : config.tenantId) || void 0;
|
|
209
252
|
function client() {
|
|
@@ -211,18 +254,19 @@ function createSupabaseAnalyticsProvider(config) {
|
|
|
211
254
|
if (!supabase) throw new Error("Supabase client not available");
|
|
212
255
|
return config.schema && config.schema !== "public" ? supabase.schema(config.schema) : supabase;
|
|
213
256
|
}
|
|
257
|
+
let batchSupported = true;
|
|
214
258
|
return {
|
|
215
259
|
async run(card, state) {
|
|
216
260
|
const { source } = card.query;
|
|
217
261
|
const reserved = reservedKeys(card);
|
|
218
262
|
if (source.kind === "fn") {
|
|
219
263
|
if (!source.run) throw new Error(`analytics: card ${card.id} declares kind 'fn' with no run()`);
|
|
220
|
-
const
|
|
264
|
+
const rows = await source.run({
|
|
221
265
|
tenantId: resolveTenantId(),
|
|
222
266
|
dateRange: state.dateRange,
|
|
223
267
|
filters: state.filters
|
|
224
268
|
});
|
|
225
|
-
return aggregateRows(
|
|
269
|
+
return aggregateRows(rows, card, state);
|
|
226
270
|
}
|
|
227
271
|
if (!source.name) throw new Error(`analytics: card ${card.id} has no source name`);
|
|
228
272
|
const isDetail = (card.query.dimensions?.length ?? 0) === 0 && (card.query.measures?.length ?? 0) === 0;
|
|
@@ -237,42 +281,80 @@ function createSupabaseAnalyticsProvider(config) {
|
|
|
237
281
|
...Object.fromEntries(Object.entries(state.filters ?? {}).map(([k, v]) => [`p_${k}`, v]))
|
|
238
282
|
});
|
|
239
283
|
if (error2) throw error2;
|
|
240
|
-
const
|
|
284
|
+
const rows = Array.isArray(data2) ? data2 : data2?.rows ?? [];
|
|
241
285
|
return {
|
|
242
|
-
rows:
|
|
243
|
-
total: Array.isArray(data2) ?
|
|
286
|
+
rows: rows.map((r) => mapRow(r, reserved, source.columnMap)),
|
|
287
|
+
total: Array.isArray(data2) ? rows.length : data2?.total ?? rows.length,
|
|
244
288
|
summary: Array.isArray(data2) ? void 0 : data2?.summary
|
|
245
289
|
};
|
|
246
290
|
}
|
|
247
|
-
const
|
|
248
|
-
const snapshot = card.query.dateColumn === null;
|
|
291
|
+
const args = runArgs(card, state, resolveTenantId());
|
|
249
292
|
const { data, error } = await client().rpc("analytics_run", {
|
|
250
|
-
p_source: source
|
|
251
|
-
p_tenant_id:
|
|
252
|
-
p_from:
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
|
293
|
+
p_source: args.source,
|
|
294
|
+
p_tenant_id: args.tenant_id,
|
|
295
|
+
p_from: args.from,
|
|
296
|
+
p_to: args.to,
|
|
297
|
+
p_dimensions: args.dimensions,
|
|
298
|
+
p_measures: args.measures,
|
|
299
|
+
p_filters: args.filters,
|
|
300
|
+
p_search: args.search,
|
|
301
|
+
p_search_columns: args.search_columns,
|
|
302
|
+
p_sort: args.sort,
|
|
303
|
+
p_dir: args.dir,
|
|
304
|
+
p_limit: args.limit,
|
|
305
|
+
p_offset: args.offset
|
|
268
306
|
});
|
|
269
307
|
if (error) throw error;
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
308
|
+
return toResult(data, card, reserved);
|
|
309
|
+
},
|
|
310
|
+
// -----------------------------------------------------------------------
|
|
311
|
+
// The batched path.
|
|
312
|
+
//
|
|
313
|
+
// Every aggregate card in the batch becomes ONE analytics_run_batch call;
|
|
314
|
+
// detail listings and 'fn' cards, which the batch function cannot express,
|
|
315
|
+
// still go one by one — but a dashboard is all aggregates, so opening
|
|
316
|
+
// /analytics went from forty-one requests to one.
|
|
317
|
+
//
|
|
318
|
+
// Results keep their INDEX. The caller matched a promise to a position, and
|
|
319
|
+
// returning a compacted array would silently hand card 7 card 9's numbers.
|
|
320
|
+
// -----------------------------------------------------------------------
|
|
321
|
+
async runMany(requests) {
|
|
322
|
+
const out = new Array(requests.length);
|
|
323
|
+
const batchable = [];
|
|
324
|
+
for (let i = 0; i < requests.length; i++) {
|
|
325
|
+
if (batchSupported && isAggregateCard(requests[i].card)) batchable.push(i);
|
|
326
|
+
}
|
|
327
|
+
const singles = requests.map((req, i) => ({ req, i })).filter(({ i }) => !batchable.includes(i)).map(({ req, i }) => this.run(req.card, req.state).then((r) => {
|
|
328
|
+
out[i] = r;
|
|
329
|
+
}).catch((e) => {
|
|
330
|
+
out[i] = e instanceof Error ? e : new Error(String(e));
|
|
331
|
+
}));
|
|
332
|
+
if (batchable.length > 0) {
|
|
333
|
+
const tenantId = resolveTenantId();
|
|
334
|
+
const payload = batchable.map((i) => runArgs(requests[i].card, requests[i].state, tenantId));
|
|
335
|
+
const { data, error } = await client().rpc("analytics_run_batch", { p_requests: payload });
|
|
336
|
+
if (error && batchUnavailable(error)) {
|
|
337
|
+
batchSupported = false;
|
|
338
|
+
await Promise.all([
|
|
339
|
+
...singles,
|
|
340
|
+
...batchable.map((i) => this.run(requests[i].card, requests[i].state).then((r) => {
|
|
341
|
+
out[i] = r;
|
|
342
|
+
}).catch((e) => {
|
|
343
|
+
out[i] = e instanceof Error ? e : new Error(String(e));
|
|
344
|
+
}))
|
|
345
|
+
]);
|
|
346
|
+
return out;
|
|
347
|
+
}
|
|
348
|
+
if (error) throw error;
|
|
349
|
+
const results = Array.isArray(data) ? data : [];
|
|
350
|
+
batchable.forEach((cardIndex, slot) => {
|
|
351
|
+
const entry = results[slot];
|
|
352
|
+
const { card } = requests[cardIndex];
|
|
353
|
+
out[cardIndex] = entry?.error ? Object.assign(new Error(String(entry.error)), { code: entry.code }) : toResult(entry, card, reservedKeys(card));
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
await Promise.all(singles);
|
|
357
|
+
return out;
|
|
276
358
|
}
|
|
277
359
|
};
|
|
278
360
|
}
|
|
@@ -286,10 +368,10 @@ function seeded(seed) {
|
|
|
286
368
|
}
|
|
287
369
|
return () => {
|
|
288
370
|
h += 1831565813;
|
|
289
|
-
let
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
return ((
|
|
371
|
+
let t = h;
|
|
372
|
+
t = Math.imul(t ^ t >>> 15, t | 1);
|
|
373
|
+
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
374
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
293
375
|
};
|
|
294
376
|
}
|
|
295
377
|
var SAMPLE_LABELS = {
|
|
@@ -338,21 +420,37 @@ function createMockAnalyticsProvider() {
|
|
|
338
420
|
return row;
|
|
339
421
|
});
|
|
340
422
|
return aggregateRows(rows, card, state);
|
|
423
|
+
},
|
|
424
|
+
// There is no transport to save a round trip on, so this is just the loop.
|
|
425
|
+
// It exists because the mock is reached through createSafeDataProvider,
|
|
426
|
+
// whose Proxy answers EVERY property with a function — `provider.runMany`
|
|
427
|
+
// is truthy even for a provider that never declared it, and the caller's
|
|
428
|
+
// feature check therefore cannot detect its absence. Implementing it is how
|
|
429
|
+
// the mock stops being a landmine behind that Proxy.
|
|
430
|
+
async runMany(requests) {
|
|
431
|
+
return Promise.all(requests.map((r) => this.run(r.card, r.state).catch((e) => e instanceof Error ? e : new Error(String(e)))));
|
|
341
432
|
}
|
|
342
433
|
};
|
|
343
434
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
435
|
+
var override;
|
|
436
|
+
function setReportsTenantId(id) {
|
|
437
|
+
override = id;
|
|
438
|
+
}
|
|
347
439
|
function getReportsTenantId() {
|
|
348
|
-
return
|
|
440
|
+
if (override) return override;
|
|
441
|
+
try {
|
|
442
|
+
return useOrganizationStore.getState().currentOrg?.id;
|
|
443
|
+
} catch {
|
|
444
|
+
return void 0;
|
|
445
|
+
}
|
|
349
446
|
}
|
|
350
447
|
|
|
351
448
|
// src/locales/en.ts
|
|
352
449
|
var en = {
|
|
353
450
|
"reports.pageTitle": "Reports",
|
|
354
451
|
"reports.pageSubtitle": "Access complete reports for analysis and decision making",
|
|
355
|
-
"reports.
|
|
452
|
+
"reports.overview": "Overview",
|
|
453
|
+
"reports.allReports": "All reports",
|
|
356
454
|
"reports.dateRange": "Date Range",
|
|
357
455
|
"reports.from": "From",
|
|
358
456
|
"reports.to": "To",
|
|
@@ -374,6 +472,17 @@ var en = {
|
|
|
374
472
|
"reports.generate": "Generate",
|
|
375
473
|
"reports.emptyStateTitle": "Ready to generate",
|
|
376
474
|
"reports.emptyStateDescription": "Select a date range and filters, then click Generate to view the report.",
|
|
475
|
+
// The assistant's invitation on /analytics. It opens on a question, not on a
|
|
476
|
+
// grid: the numbers are already downstairs, and what the owner actually wants
|
|
477
|
+
// is to know what they mean.
|
|
478
|
+
"reports.assistant.persona": "Growth Analyst",
|
|
479
|
+
"reports.assistant.headline": "Hi {{name}}, what would you like to learn today?",
|
|
480
|
+
"reports.assistant.pitch": "Let's find the opportunities hiding in your business",
|
|
481
|
+
"reports.assistant.description": "Tell me your goals and I'll help build a plan to hit them. I read the same data these reports do, and I'll point at what changed and what to do about it.",
|
|
482
|
+
"reports.assistant.placeholder": "Build a custom report...",
|
|
483
|
+
"reports.assistant.suggestion.0": "How is my business doing this month?",
|
|
484
|
+
"reports.assistant.suggestion.1": "What's growing and what's falling?",
|
|
485
|
+
"reports.assistant.suggestion.2": "Where am I losing money?",
|
|
377
486
|
"reports.unavailableTitle": "Coming soon",
|
|
378
487
|
"reports.unavailableDescription": "This report is not available yet. It will be enabled in a future update."
|
|
379
488
|
};
|
|
@@ -382,7 +491,8 @@ var en = {
|
|
|
382
491
|
var ptBR = {
|
|
383
492
|
"reports.pageTitle": "Relat\xF3rios",
|
|
384
493
|
"reports.pageSubtitle": "Acesse relat\xF3rios completos para an\xE1lise e tomada de decis\xE3o",
|
|
385
|
-
"reports.
|
|
494
|
+
"reports.overview": "Vis\xE3o geral",
|
|
495
|
+
"reports.allReports": "Todos os relat\xF3rios",
|
|
386
496
|
"reports.dateRange": "Per\xEDodo",
|
|
387
497
|
"reports.from": "De",
|
|
388
498
|
"reports.to": "At\xE9",
|
|
@@ -404,6 +514,14 @@ var ptBR = {
|
|
|
404
514
|
"reports.generate": "Gerar",
|
|
405
515
|
"reports.emptyStateTitle": "Pronto para gerar",
|
|
406
516
|
"reports.emptyStateDescription": "Selecione o per\xEDodo e os filtros desejados, depois clique em Gerar para visualizar o relat\xF3rio.",
|
|
517
|
+
"reports.assistant.persona": "Analista de Crescimento",
|
|
518
|
+
"reports.assistant.headline": "Oi {{name}}, o que voc\xEA quer entender hoje?",
|
|
519
|
+
"reports.assistant.pitch": "Vamos achar as oportunidades escondidas no seu neg\xF3cio",
|
|
520
|
+
"reports.assistant.description": "Me conte sua meta e eu ajudo a montar o plano para bater. Leio os mesmos dados destes relat\xF3rios e aponto o que mudou e o que fazer a respeito.",
|
|
521
|
+
"reports.assistant.placeholder": "Pe\xE7a um relat\xF3rio do seu jeito...",
|
|
522
|
+
"reports.assistant.suggestion.0": "Como est\xE1 meu neg\xF3cio neste m\xEAs?",
|
|
523
|
+
"reports.assistant.suggestion.1": "O que est\xE1 crescendo e o que est\xE1 caindo?",
|
|
524
|
+
"reports.assistant.suggestion.2": "Onde estou perdendo dinheiro?",
|
|
407
525
|
"reports.unavailableTitle": "Em breve",
|
|
408
526
|
"reports.unavailableDescription": "Este relat\xF3rio ainda n\xE3o est\xE1 dispon\xEDvel. Ser\xE1 habilitado em uma atualiza\xE7\xE3o futura."
|
|
409
527
|
};
|
|
@@ -493,8 +611,8 @@ function markViewed(cardId) {
|
|
|
493
611
|
for (const listener of listeners) listener(next);
|
|
494
612
|
}
|
|
495
613
|
function useLastViewed() {
|
|
496
|
-
const [map, setMap] =
|
|
497
|
-
|
|
614
|
+
const [map, setMap] = React5.useState(() => read());
|
|
615
|
+
React5.useEffect(() => {
|
|
498
616
|
listeners.add(setMap);
|
|
499
617
|
return () => {
|
|
500
618
|
listeners.delete(setMap);
|
|
@@ -504,16 +622,16 @@ function useLastViewed() {
|
|
|
504
622
|
}
|
|
505
623
|
var ANALYTICS_BASE = "/analytics";
|
|
506
624
|
var REPORTS_BASE = `${ANALYTICS_BASE}/reports`;
|
|
507
|
-
var ConfigContext =
|
|
625
|
+
var ConfigContext = React5__default.createContext(null);
|
|
508
626
|
function useAnalyticsConfig() {
|
|
509
|
-
const ctx =
|
|
627
|
+
const ctx = React5__default.useContext(ConfigContext);
|
|
510
628
|
if (!ctx) throw new Error("useAnalyticsConfig must be used inside <AnalyticsSurface>");
|
|
511
629
|
return ctx;
|
|
512
630
|
}
|
|
513
631
|
function useAnalyticsCards() {
|
|
514
632
|
const runtime = usePluginRuntime();
|
|
515
633
|
const { cards, createdBy } = useAnalyticsConfig();
|
|
516
|
-
return
|
|
634
|
+
return React5__default.useMemo(() => {
|
|
517
635
|
const byId = /* @__PURE__ */ new Map();
|
|
518
636
|
for (const card of getAnalyticsCards(runtime)) byId.set(card.id, card);
|
|
519
637
|
for (const card of cards) byId.set(card.id, card);
|
|
@@ -522,7 +640,7 @@ function useAnalyticsCards() {
|
|
|
522
640
|
}
|
|
523
641
|
function useOverviewCards() {
|
|
524
642
|
const all = useAnalyticsCards();
|
|
525
|
-
return
|
|
643
|
+
return React5__default.useMemo(
|
|
526
644
|
() => all.filter((c) => c.dashboard && c.dashboard.defaultVisible !== false).sort((a, b) => (a.dashboard?.order ?? 0) - (b.dashboard?.order ?? 0)),
|
|
527
645
|
[all]
|
|
528
646
|
);
|
|
@@ -549,7 +667,7 @@ function navigateAnalytics(path) {
|
|
|
549
667
|
hashRouterAdapter().navigate(path);
|
|
550
668
|
}
|
|
551
669
|
function AnalyticsSurface({ config, provider, children }) {
|
|
552
|
-
const openCard =
|
|
670
|
+
const openCard = React5__default.useCallback((card) => {
|
|
553
671
|
markViewed(card.id);
|
|
554
672
|
navigateAnalytics(`${REPORTS_BASE}/${encodeURIComponent(card.id)}`);
|
|
555
673
|
}, []);
|
|
@@ -567,21 +685,141 @@ function AnalyticsSurface({ config, provider, children }) {
|
|
|
567
685
|
}
|
|
568
686
|
) });
|
|
569
687
|
}
|
|
688
|
+
var reportRoute = (cardId) => `${REPORTS_BASE}/${encodeURIComponent(cardId)}`;
|
|
689
|
+
function groupCards(cards) {
|
|
690
|
+
const groups = /* @__PURE__ */ new Map();
|
|
691
|
+
for (const card of cards) {
|
|
692
|
+
const list = groups.get(card.category);
|
|
693
|
+
if (list) list.push(card);
|
|
694
|
+
else groups.set(card.category, [card]);
|
|
695
|
+
}
|
|
696
|
+
return [...groups.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([category, list]) => [category, [...list].sort((a, b) => a.name.localeCompare(b.name))]);
|
|
697
|
+
}
|
|
698
|
+
function useAnalyticsTree() {
|
|
699
|
+
const t = useTranslation();
|
|
700
|
+
const { labels, showLiveView } = useAnalyticsConfig();
|
|
701
|
+
const cards = useAnalyticsCards();
|
|
702
|
+
return React5__default.useMemo(() => ({
|
|
703
|
+
title: labels.analytics,
|
|
704
|
+
// Only the overview. The searchable catalogue (/analytics/reports) is still
|
|
705
|
+
// routed — bookmarks and the legacy /reports link resolve — but it is not
|
|
706
|
+
// listed: the tree below already IS the catalogue, and a link named "all
|
|
707
|
+
// reports" sitting above the reports reads as a different, fuller list.
|
|
708
|
+
fixed: [
|
|
709
|
+
{ id: "overview", label: labels.overview ?? t("reports.overview"), icon: "BarChart3", route: ANALYTICS_BASE, view: "overview" }
|
|
710
|
+
],
|
|
711
|
+
groups: groupCards(cards),
|
|
712
|
+
live: showLiveView ? { id: "live", label: labels.liveView, icon: "Radio", route: `${ANALYTICS_BASE}/live`, view: "live" } : null
|
|
713
|
+
}), [labels, showLiveView, cards, t]);
|
|
714
|
+
}
|
|
715
|
+
function AnalyticsModule({ view, cardId, children }) {
|
|
716
|
+
const variant = useModuleLayout();
|
|
717
|
+
const tree = useAnalyticsTree();
|
|
718
|
+
const activeId = cardId ? decodeURIComponent(cardId) : void 0;
|
|
719
|
+
const shellRail = useShellRailAvailable();
|
|
720
|
+
const claimsRail = shellRail ?? variant !== "rail";
|
|
721
|
+
const entries = React5__default.useMemo(() => [
|
|
722
|
+
...tree.fixed.map((f) => ({
|
|
723
|
+
id: `analytics:${f.id}`,
|
|
724
|
+
label: f.label,
|
|
725
|
+
icon: f.icon,
|
|
726
|
+
route: f.route,
|
|
727
|
+
section: "main"
|
|
728
|
+
})),
|
|
729
|
+
...tree.groups.flatMap(([category, list]) => list.map((card) => ({
|
|
730
|
+
id: `analytics:${card.id}`,
|
|
731
|
+
label: card.name,
|
|
732
|
+
icon: card.icon ?? "FileText",
|
|
733
|
+
route: reportRoute(card.id),
|
|
734
|
+
section: "main",
|
|
735
|
+
// Consecutive items sharing a group render under one heading — the same
|
|
736
|
+
// mechanism that heads the settings rail's plugin runs.
|
|
737
|
+
group: category
|
|
738
|
+
}))),
|
|
739
|
+
...tree.live ? [{
|
|
740
|
+
id: "analytics:live",
|
|
741
|
+
label: tree.live.label,
|
|
742
|
+
icon: tree.live.icon,
|
|
743
|
+
route: tree.live.route,
|
|
744
|
+
section: "main"
|
|
745
|
+
}] : []
|
|
746
|
+
], [tree]);
|
|
747
|
+
useContextualNav(claimsRail ? { base: ANALYTICS_BASE, entries, label: tree.title } : null);
|
|
748
|
+
const nav = React5__default.useMemo(() => {
|
|
749
|
+
if (claimsRail) return [];
|
|
750
|
+
return [
|
|
751
|
+
...tree.fixed.map((f) => ({
|
|
752
|
+
id: f.id,
|
|
753
|
+
label: f.label,
|
|
754
|
+
icon: f.icon,
|
|
755
|
+
active: view === f.view,
|
|
756
|
+
onClick: () => navigateAnalytics(f.route)
|
|
757
|
+
})),
|
|
758
|
+
...tree.groups.map(([category, list]) => ({
|
|
759
|
+
id: `group:${category}`,
|
|
760
|
+
label: category,
|
|
761
|
+
children: list.map((card) => ({
|
|
762
|
+
id: card.id,
|
|
763
|
+
label: card.name,
|
|
764
|
+
active: view === "report" && card.id === activeId,
|
|
765
|
+
onClick: () => navigateAnalytics(reportRoute(card.id))
|
|
766
|
+
}))
|
|
767
|
+
})),
|
|
768
|
+
...tree.live ? [{
|
|
769
|
+
id: tree.live.id,
|
|
770
|
+
label: tree.live.label,
|
|
771
|
+
icon: tree.live.icon,
|
|
772
|
+
active: view === "live",
|
|
773
|
+
onClick: () => navigateAnalytics(tree.live.route)
|
|
774
|
+
}] : []
|
|
775
|
+
];
|
|
776
|
+
}, [tree, view, activeId, claimsRail]);
|
|
777
|
+
return /* @__PURE__ */ jsx(
|
|
778
|
+
ModulePage,
|
|
779
|
+
{
|
|
780
|
+
title: tree.title,
|
|
781
|
+
nav,
|
|
782
|
+
navVariant: claimsRail ? "sidebar" : "rail",
|
|
783
|
+
showHeader: false,
|
|
784
|
+
viewKey: view === "report" ? `report:${activeId}` : view,
|
|
785
|
+
children
|
|
786
|
+
}
|
|
787
|
+
);
|
|
788
|
+
}
|
|
570
789
|
function AnalyticsOverview() {
|
|
571
|
-
const { labels, showHeader } = useAnalyticsConfig();
|
|
790
|
+
const { labels, showHeader, assistant } = useAnalyticsConfig();
|
|
572
791
|
const cards = useOverviewCards();
|
|
573
|
-
const filters =
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
792
|
+
const filters = React5__default.useMemo(() => collectFilters(cards), [cards]);
|
|
793
|
+
const t = useTranslation();
|
|
794
|
+
const suggestions = React5__default.useMemo(
|
|
795
|
+
() => assistant?.suggestions ?? [0, 1, 2].map((i) => ({ label: t(`reports.assistant.suggestion.${i}`) })),
|
|
796
|
+
[assistant?.suggestions, t]
|
|
797
|
+
);
|
|
798
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
799
|
+
assistant && /* @__PURE__ */ jsx(
|
|
800
|
+
AssistantInvite,
|
|
801
|
+
{
|
|
802
|
+
id: "analytics",
|
|
803
|
+
persona: assistant.persona ?? t("reports.assistant.persona"),
|
|
804
|
+
headline: assistant.headline ?? t("reports.assistant.headline"),
|
|
805
|
+
pitch: assistant.pitch ?? t("reports.assistant.pitch"),
|
|
806
|
+
description: assistant.description ?? t("reports.assistant.description"),
|
|
807
|
+
placeholder: assistant.placeholder ?? t("reports.assistant.placeholder"),
|
|
808
|
+
suggestions
|
|
809
|
+
}
|
|
810
|
+
),
|
|
811
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
812
|
+
showHeader ? /* @__PURE__ */ jsxs("div", { children: [
|
|
813
|
+
/* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold tracking-tight", children: labels.analytics }),
|
|
814
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: labels.overviewSubtitle })
|
|
815
|
+
] }) : null,
|
|
816
|
+
/* @__PURE__ */ jsx(AnalyticsControls, { filters, className: "sticky top-3 z-20" }),
|
|
817
|
+
cards.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center rounded-xl border border-dashed py-16 text-center", children: [
|
|
818
|
+
/* @__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" }) }),
|
|
819
|
+
/* @__PURE__ */ jsx("p", { className: "font-medium", children: labels.emptyOverviewTitle }),
|
|
820
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 max-w-sm text-sm text-muted-foreground", children: labels.emptyOverviewDescription })
|
|
821
|
+
] }) : /* @__PURE__ */ jsx(AnalyticsGrid, { cards })
|
|
822
|
+
] })
|
|
585
823
|
] });
|
|
586
824
|
}
|
|
587
825
|
function formatViewed(iso, locale, never) {
|
|
@@ -594,18 +832,18 @@ function ReportsListPage() {
|
|
|
594
832
|
const { labels, currency, showHeader } = useAnalyticsConfig();
|
|
595
833
|
const cards = useAnalyticsCards();
|
|
596
834
|
const lastViewed = useLastViewed();
|
|
597
|
-
const [search, setSearch] =
|
|
598
|
-
const [category, setCategory] =
|
|
599
|
-
const [author, setAuthor] =
|
|
600
|
-
const categoryOptions =
|
|
835
|
+
const [search, setSearch] = React5__default.useState("");
|
|
836
|
+
const [category, setCategory] = React5__default.useState();
|
|
837
|
+
const [author, setAuthor] = React5__default.useState();
|
|
838
|
+
const categoryOptions = React5__default.useMemo(
|
|
601
839
|
() => [...new Set(cards.map((c) => c.category))].sort((a, b) => a.localeCompare(b)).map((c) => ({ value: c, label: c })),
|
|
602
840
|
[cards]
|
|
603
841
|
);
|
|
604
|
-
const authorOptions =
|
|
842
|
+
const authorOptions = React5__default.useMemo(
|
|
605
843
|
() => [...new Set(cards.map((c) => c.createdBy ?? "\u2014"))].sort((a, b) => a.localeCompare(b)).map((a) => ({ value: a, label: a })),
|
|
606
844
|
[cards]
|
|
607
845
|
);
|
|
608
|
-
const rows =
|
|
846
|
+
const rows = React5__default.useMemo(() => {
|
|
609
847
|
const q = search.trim().toLowerCase();
|
|
610
848
|
return cards.filter((card) => {
|
|
611
849
|
if (category && card.category !== category) return false;
|
|
@@ -614,7 +852,7 @@ function ReportsListPage() {
|
|
|
614
852
|
return card.name.toLowerCase().includes(q) || card.description?.toLowerCase().includes(q) || card.category.toLowerCase().includes(q);
|
|
615
853
|
});
|
|
616
854
|
}, [cards, search, category, author]);
|
|
617
|
-
const columns =
|
|
855
|
+
const columns = React5__default.useMemo(() => [
|
|
618
856
|
{
|
|
619
857
|
id: "name",
|
|
620
858
|
accessorFn: (card) => card.name,
|
|
@@ -730,7 +968,7 @@ function exportToCsv(columns, data, filename, currency) {
|
|
|
730
968
|
exportCSV(csvColumns, data, filename);
|
|
731
969
|
}
|
|
732
970
|
function ExportDropdown({ columns, data, filename, currency, title }) {
|
|
733
|
-
const
|
|
971
|
+
const t = useTranslation();
|
|
734
972
|
const [excelAvailable, setExcelAvailable] = useState(false);
|
|
735
973
|
const [pdfAvailable, setPdfAvailable] = useState(false);
|
|
736
974
|
useEffect(() => {
|
|
@@ -751,12 +989,12 @@ function ExportDropdown({ columns, data, filename, currency, title }) {
|
|
|
751
989
|
return /* @__PURE__ */ jsxs(Dropdown, { children: [
|
|
752
990
|
/* @__PURE__ */ jsx(DropdownTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", children: [
|
|
753
991
|
/* @__PURE__ */ jsx(Download, { className: "h-4 w-4 mr-1.5" }),
|
|
754
|
-
|
|
992
|
+
t("reports.export")
|
|
755
993
|
] }) }),
|
|
756
994
|
/* @__PURE__ */ jsxs(DropdownContent, { align: "end", children: [
|
|
757
|
-
/* @__PURE__ */ jsx(DropdownItem, { onClick: handleCsv, children:
|
|
758
|
-
excelAvailable && /* @__PURE__ */ jsx(DropdownItem, { onClick: handleExcel, children:
|
|
759
|
-
pdfAvailable && /* @__PURE__ */ jsx(DropdownItem, { onClick: handlePdf, children:
|
|
995
|
+
/* @__PURE__ */ jsx(DropdownItem, { onClick: handleCsv, children: t("reports.exportCsv") }),
|
|
996
|
+
excelAvailable && /* @__PURE__ */ jsx(DropdownItem, { onClick: handleExcel, children: t("reports.exportExcel") }),
|
|
997
|
+
pdfAvailable && /* @__PURE__ */ jsx(DropdownItem, { onClick: handlePdf, children: t("reports.exportPdf") })
|
|
760
998
|
] })
|
|
761
999
|
] });
|
|
762
1000
|
}
|
|
@@ -813,8 +1051,8 @@ function ViewerBody({ card }) {
|
|
|
813
1051
|
const { labels, currency } = useAnalyticsConfig();
|
|
814
1052
|
const { state, setPage, setPageSize } = useAnalyticsContext();
|
|
815
1053
|
const { result, loading, error, engineMissing } = useAnalyticsCard(card);
|
|
816
|
-
const columns =
|
|
817
|
-
const tanstackColumns =
|
|
1054
|
+
const columns = React5__default.useMemo(() => cardColumns(card), [card]);
|
|
1055
|
+
const tanstackColumns = React5__default.useMemo(
|
|
818
1056
|
() => reportColumnsToTanstack(columns, currency.code),
|
|
819
1057
|
[columns, currency.code]
|
|
820
1058
|
);
|
|
@@ -871,7 +1109,7 @@ function AnalyticsReportViewer({ cardId }) {
|
|
|
871
1109
|
const { labels } = useAnalyticsConfig();
|
|
872
1110
|
const cards = useAnalyticsCards();
|
|
873
1111
|
const card = cards.find((c) => c.id === decodeURIComponent(cardId ?? ""));
|
|
874
|
-
|
|
1112
|
+
React5__default.useEffect(() => {
|
|
875
1113
|
if (card) markViewed(card.id);
|
|
876
1114
|
}, [card?.id]);
|
|
877
1115
|
if (!card) {
|
|
@@ -888,8 +1126,8 @@ function AnalyticsReportViewer({ cardId }) {
|
|
|
888
1126
|
SubpageHeader,
|
|
889
1127
|
{
|
|
890
1128
|
title: card.name,
|
|
891
|
-
parentLabel: labels.
|
|
892
|
-
onBack: () => navigateAnalytics(
|
|
1129
|
+
parentLabel: labels.analytics,
|
|
1130
|
+
onBack: () => navigateAnalytics(ANALYTICS_BASE)
|
|
893
1131
|
}
|
|
894
1132
|
),
|
|
895
1133
|
card.description ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: card.description }) : null,
|
|
@@ -908,7 +1146,7 @@ function AnalyticsReportViewer({ cardId }) {
|
|
|
908
1146
|
function ViewerActions({ card }) {
|
|
909
1147
|
const { currency } = useAnalyticsConfig();
|
|
910
1148
|
const { result } = useAnalyticsCard(card);
|
|
911
|
-
const columns =
|
|
1149
|
+
const columns = React5__default.useMemo(() => cardColumns(card), [card]);
|
|
912
1150
|
if (!result?.rows?.length) return null;
|
|
913
1151
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
914
1152
|
/* @__PURE__ */ jsx(
|
|
@@ -1099,7 +1337,9 @@ function resolveConfig(options) {
|
|
|
1099
1337
|
defaultPageSize: options.defaultPageSize ?? 50,
|
|
1100
1338
|
showHeader: options.showHeader === true,
|
|
1101
1339
|
defaultRange: options.defaultRange ?? "last_30_days",
|
|
1102
|
-
defaultCompare: options.defaultCompare ?? "previous_period"
|
|
1340
|
+
defaultCompare: options.defaultCompare ?? "previous_period",
|
|
1341
|
+
showLiveView: options.showLiveView === true,
|
|
1342
|
+
assistant: options.assistant === false ? null : options.assistant ?? {}
|
|
1103
1343
|
};
|
|
1104
1344
|
}
|
|
1105
1345
|
function createAnalyticsPlugin(options = {}) {
|
|
@@ -1109,10 +1349,14 @@ function createAnalyticsPlugin(options = {}) {
|
|
|
1109
1349
|
() => createSupabaseAnalyticsProvider({ tenantId: () => getReportsTenantId() }),
|
|
1110
1350
|
() => createMockAnalyticsProvider()
|
|
1111
1351
|
);
|
|
1112
|
-
const page2 = (Body) => (props) =>
|
|
1352
|
+
const page2 = (Body, view) => (props) => React5__default.createElement(
|
|
1113
1353
|
AnalyticsSurface,
|
|
1114
1354
|
{ config, provider },
|
|
1115
|
-
|
|
1355
|
+
React5__default.createElement(
|
|
1356
|
+
AnalyticsModule,
|
|
1357
|
+
{ view, cardId: props?.cardId },
|
|
1358
|
+
React5__default.createElement(Body, props)
|
|
1359
|
+
)
|
|
1116
1360
|
);
|
|
1117
1361
|
const READ = { feature: "reports", action: "read" };
|
|
1118
1362
|
const position = options.navPosition ?? 9;
|
|
@@ -1125,23 +1369,7 @@ function createAnalyticsPlugin(options = {}) {
|
|
|
1125
1369
|
route: ANALYTICS_BASE,
|
|
1126
1370
|
icon: "BarChart3",
|
|
1127
1371
|
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
|
-
}] : []
|
|
1372
|
+
}
|
|
1145
1373
|
];
|
|
1146
1374
|
return {
|
|
1147
1375
|
id: "reports",
|
|
@@ -1157,8 +1385,8 @@ function createAnalyticsPlugin(options = {}) {
|
|
|
1157
1385
|
],
|
|
1158
1386
|
navigation: nav,
|
|
1159
1387
|
routes: [
|
|
1160
|
-
{ path: ANALYTICS_BASE, component: page2(AnalyticsOverview), permission: READ },
|
|
1161
|
-
{ path: REPORTS_BASE, component: page2(ReportsListPage), permission: READ },
|
|
1388
|
+
{ path: ANALYTICS_BASE, component: page2(AnalyticsOverview, "overview"), permission: READ },
|
|
1389
|
+
{ path: REPORTS_BASE, component: page2(ReportsListPage, "reports"), permission: READ },
|
|
1162
1390
|
// Registered before the list would ever shadow it: matchRoute scores
|
|
1163
1391
|
// static segments above params, so /analytics/reports still wins its own
|
|
1164
1392
|
// exact path and only /analytics/reports/<id> lands here.
|
|
@@ -1166,14 +1394,17 @@ function createAnalyticsPlugin(options = {}) {
|
|
|
1166
1394
|
// (the card's name), so it claims the shell bar itself via PageHeader.
|
|
1167
1395
|
// Declaring both gave the page two stacked back-crumbs saying the same
|
|
1168
1396
|
// 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 },
|
|
1397
|
+
{ path: `${REPORTS_BASE}/:cardId`, component: page2(AnalyticsReportViewer, "report"), permission: READ },
|
|
1398
|
+
{ path: `${ANALYTICS_BASE}/live`, component: page2(LiveViewPage, "live"), permission: READ },
|
|
1171
1399
|
// The old top-level route kept as a redirect target: bookmarks, saved
|
|
1172
1400
|
// links and the five apps still pointing at /reports must not 404.
|
|
1173
|
-
{ path: "/reports", component: page2(ReportsListPage), permission: READ }
|
|
1401
|
+
{ path: "/reports", component: page2(ReportsListPage, "reports"), permission: READ }
|
|
1174
1402
|
],
|
|
1175
1403
|
widgets: [],
|
|
1176
1404
|
registries: [],
|
|
1405
|
+
// Measurement, not acquisition — what the Fayz plugin page lists for
|
|
1406
|
+
// Analytics.
|
|
1407
|
+
integrations: ["google-analytics", "hotjar"],
|
|
1177
1408
|
locales: reportsLocales
|
|
1178
1409
|
};
|
|
1179
1410
|
}
|
|
@@ -1194,6 +1425,6 @@ function createReportsPlugin(options) {
|
|
|
1194
1425
|
});
|
|
1195
1426
|
}
|
|
1196
1427
|
|
|
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 };
|
|
1428
|
+
export { ANALYTICS_BASE, AnalyticsModule, AnalyticsOverview, AnalyticsReportViewer, AnalyticsSurface, REPORTS_BASE, ReportsListPage, aggregateRows, cardColumns, createAnalyticsPlugin, createMockAnalyticsProvider, createMockReportProvider, createReportsPlugin, createSupabaseAnalyticsProvider, createSupabaseReportProvider, getReportsTenantId, markViewed, reportDefToCard, reportDefsToCards, setReportsTenantId, useAnalyticsCards, useAnalyticsConfig, useLastViewed, useOverviewCards };
|
|
1198
1429
|
//# sourceMappingURL=index.js.map
|
|
1199
1430
|
//# sourceMappingURL=index.js.map
|