@fayz-ai/plugin-reports 0.16.29 → 0.16.30

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.
@@ -0,0 +1,4 @@
1
+ export { AnalyticsModule } from './chunk-67JOMNSD.js';
2
+ import './chunk-YVLQV4N7.js';
3
+ //# sourceMappingURL=AnalyticsModule-YIH6MDCC.js.map
4
+ //# sourceMappingURL=AnalyticsModule-YIH6MDCC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"AnalyticsModule-YIH6MDCC.js"}
@@ -0,0 +1,4 @@
1
+ export { AnalyticsOverview } from './chunk-S4MICM4C.js';
2
+ import './chunk-YVLQV4N7.js';
3
+ //# sourceMappingURL=AnalyticsOverview-R3JEMUQL.js.map
4
+ //# sourceMappingURL=AnalyticsOverview-R3JEMUQL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"AnalyticsOverview-R3JEMUQL.js"}
@@ -0,0 +1,5 @@
1
+ export { AnalyticsReportViewer } from './chunk-LB6NOSJU.js';
2
+ import './chunk-YVLQV4N7.js';
3
+ import './chunk-4QIHTUXD.js';
4
+ //# sourceMappingURL=AnalyticsReportViewer-U7SDU2LL.js.map
5
+ //# sourceMappingURL=AnalyticsReportViewer-U7SDU2LL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"AnalyticsReportViewer-U7SDU2LL.js"}
@@ -0,0 +1,4 @@
1
+ export { AnalyticsSettingsPage, buildCustomReport } from './chunk-DTUN4WLI.js';
2
+ import './chunk-YVLQV4N7.js';
3
+ //# sourceMappingURL=AnalyticsSettingsPage-XWPM3VSA.js.map
4
+ //# sourceMappingURL=AnalyticsSettingsPage-XWPM3VSA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"AnalyticsSettingsPage-XWPM3VSA.js"}
@@ -0,0 +1,16 @@
1
+ import { useAnalyticsConfig } from './chunk-YVLQV4N7.js';
2
+ import { Radio } from 'lucide-react';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+
5
+ function LiveViewPage() {
6
+ const { labels } = useAnalyticsConfig();
7
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center rounded-xl border border-dashed py-20 text-center", children: [
8
+ /* @__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" }) }),
9
+ /* @__PURE__ */ jsx("p", { className: "font-medium", children: labels.liveView }),
10
+ /* @__PURE__ */ jsx("p", { className: "mt-1 max-w-sm text-sm text-muted-foreground", children: labels.liveViewSoon })
11
+ ] });
12
+ }
13
+
14
+ export { LiveViewPage };
15
+ //# sourceMappingURL=LiveViewPage-XMVZHJ3U.js.map
16
+ //# sourceMappingURL=LiveViewPage-XMVZHJ3U.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/views/LiveViewPage.tsx"],"names":[],"mappings":";;;;AAaO,SAAS,YAAA,GAAe;AAC7B,EAAA,MAAM,EAAE,MAAA,EAAO,GAAI,kBAAA,EAAmB;AACtC,EAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,6FAAA,EACb,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,SAAI,SAAA,EAAU,uEAAA,EACb,8BAAC,KAAA,EAAA,EAAM,SAAA,EAAU,iCAAgC,CAAA,EACnD,CAAA;AAAA,oBACA,GAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,aAAA,EAAe,iBAAO,QAAA,EAAS,CAAA;AAAA,oBAC5C,GAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,6CAAA,EAA+C,iBAAO,YAAA,EAAa;AAAA,GAAA,EAClF,CAAA;AAEJ","file":"LiveViewPage-XMVZHJ3U.js","sourcesContent":["import React from 'react'\nimport { Radio } from 'lucide-react'\nimport { useAnalyticsConfig } from './AnalyticsSurface'\n\n// ---------------------------------------------------------------------------\n// Live View — the real-time surface.\n//\n// A deliberate stub. It needs a storefront event stream (sessions, carts,\n// checkouts) that does not exist yet, and the honest thing on a page with no\n// data source is to say so rather than render zeroes that look like a quiet\n// afternoon. It holds its nav slot so the shape of the section is visible.\n// ---------------------------------------------------------------------------\n\nexport function LiveViewPage() {\n const { labels } = useAnalyticsConfig()\n return (\n <div className=\"flex flex-col items-center justify-center rounded-xl border border-dashed py-20 text-center\">\n <div className=\"mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-muted\">\n <Radio className=\"h-5 w-5 text-muted-foreground\" />\n </div>\n <p className=\"font-medium\">{labels.liveView}</p>\n <p className=\"mt-1 max-w-sm text-sm text-muted-foreground\">{labels.liveViewSoon}</p>\n </div>\n )\n}\n"]}
@@ -0,0 +1,4 @@
1
+ export { ReportsListPage } from './chunk-DP7S37Y2.js';
2
+ import './chunk-YVLQV4N7.js';
3
+ //# sourceMappingURL=ReportsListPage-7FAKQZ4T.js.map
4
+ //# sourceMappingURL=ReportsListPage-7FAKQZ4T.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"ReportsListPage-7FAKQZ4T.js"}
@@ -0,0 +1,127 @@
1
+ import { useAnalyticsConfig, navigateAnalytics, useAnalyticsCards, SETTINGS_ROUTE, ANALYTICS_BASE, REPORTS_BASE } from './chunk-YVLQV4N7.js';
2
+ import React2 from 'react';
3
+ import { useModuleLayout, ModulePage } from '@fayz-ai/ui';
4
+ import { useTranslation } from '@fayz-ai/core';
5
+ import { AssistantInvite } from '@fayz-ai/admin';
6
+ import { jsxs, jsx } from 'react/jsx-runtime';
7
+
8
+ function AnalyticsAssistant() {
9
+ const { assistant } = useAnalyticsConfig();
10
+ const t = useTranslation();
11
+ const suggestions = React2.useMemo(
12
+ () => assistant?.suggestions ?? [0, 1, 2].map((i) => ({ label: t(`reports.assistant.suggestion.${i}`) })),
13
+ [assistant?.suggestions, t]
14
+ );
15
+ if (!assistant) return null;
16
+ return /* @__PURE__ */ jsx(
17
+ AssistantInvite,
18
+ {
19
+ id: "analytics",
20
+ auto: assistant.auto ?? false,
21
+ role: assistant.role ?? "growth",
22
+ persona: assistant.persona ?? t("reports.assistant.persona"),
23
+ headline: assistant.headline ?? t("reports.assistant.headline"),
24
+ pitch: assistant.pitch ?? t("reports.assistant.pitch"),
25
+ description: assistant.description ?? t("reports.assistant.description"),
26
+ placeholder: assistant.placeholder ?? t("reports.assistant.placeholder"),
27
+ suggestions
28
+ }
29
+ );
30
+ }
31
+ var reportRoute = (cardId) => `${REPORTS_BASE}/${encodeURIComponent(cardId)}`;
32
+ function groupCards(cards) {
33
+ const groups = /* @__PURE__ */ new Map();
34
+ for (const card of cards) {
35
+ const list = groups.get(card.category);
36
+ if (list) list.push(card);
37
+ else groups.set(card.category, [card]);
38
+ }
39
+ return [...groups.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([category, list]) => [category, [...list].sort((a, b) => a.name.localeCompare(b.name))]);
40
+ }
41
+ function useAnalyticsTree() {
42
+ const t = useTranslation();
43
+ const { labels, showLiveView } = useAnalyticsConfig();
44
+ const cards = useAnalyticsCards();
45
+ return React2.useMemo(() => ({
46
+ title: labels.analytics,
47
+ // Only the overview. The searchable catalogue (/analytics/reports) is still
48
+ // routed — bookmarks and the legacy /reports link resolve — but it is not
49
+ // listed: the tree below already IS the catalogue, and a link named "all
50
+ // reports" sitting above the reports reads as a different, fuller list.
51
+ fixed: [
52
+ { id: "overview", label: labels.overview ?? t("reports.overview"), icon: "BarChart3", route: ANALYTICS_BASE, view: "overview" }
53
+ ],
54
+ groups: groupCards(cards),
55
+ live: showLiveView ? { id: "live", label: labels.liveView, icon: "Radio", route: `${ANALYTICS_BASE}/live`, view: "live" } : null,
56
+ // Depois dos relatórios, não antes: é a tela que fala SOBRE a lista, e uma
57
+ // engrenagem no topo empurra para baixo o que a pessoa veio ver.
58
+ settings: {
59
+ id: "settings",
60
+ label: labels.settings ?? t("reports.settings.nav"),
61
+ icon: "SlidersHorizontal",
62
+ route: SETTINGS_ROUTE,
63
+ view: "settings"
64
+ }
65
+ }), [labels, showLiveView, cards, t]);
66
+ }
67
+ function AnalyticsModule({ view, cardId, children }) {
68
+ const t = useTranslation();
69
+ const { labels } = useAnalyticsConfig();
70
+ useModuleLayout();
71
+ const tree = useAnalyticsTree();
72
+ const activeId = cardId ? decodeURIComponent(cardId) : void 0;
73
+ const nav = React2.useMemo(() => {
74
+ return [
75
+ ...tree.fixed.map((f) => ({
76
+ id: f.id,
77
+ label: f.label,
78
+ icon: f.icon,
79
+ active: view === f.view,
80
+ onClick: () => navigateAnalytics(f.route)
81
+ })),
82
+ ...tree.groups.map(([category, list]) => ({
83
+ id: `group:${category}`,
84
+ label: category,
85
+ children: list.map((card) => ({
86
+ id: card.id,
87
+ label: card.name,
88
+ active: view === "report" && card.id === activeId,
89
+ onClick: () => navigateAnalytics(reportRoute(card.id))
90
+ }))
91
+ })),
92
+ ...tree.live ? [{
93
+ id: tree.live.id,
94
+ label: tree.live.label,
95
+ icon: tree.live.icon,
96
+ active: view === "live",
97
+ onClick: () => navigateAnalytics(tree.live.route)
98
+ }] : [],
99
+ {
100
+ id: tree.settings.id,
101
+ label: tree.settings.label,
102
+ icon: tree.settings.icon,
103
+ active: view === "settings",
104
+ onClick: () => navigateAnalytics(tree.settings.route)
105
+ }
106
+ ];
107
+ }, [tree, view, activeId]);
108
+ return /* @__PURE__ */ jsxs(
109
+ ModulePage,
110
+ {
111
+ title: tree.title,
112
+ nav,
113
+ navVariant: "rail",
114
+ navFilterPlaceholder: labels.searchReports ?? t("reports.search"),
115
+ showHeader: false,
116
+ viewKey: view === "report" ? `report:${activeId}` : view,
117
+ children: [
118
+ /* @__PURE__ */ jsx(AnalyticsAssistant, {}),
119
+ /* @__PURE__ */ jsx("div", { className: "md:pb-16", children })
120
+ ]
121
+ }
122
+ );
123
+ }
124
+
125
+ export { AnalyticsModule };
126
+ //# sourceMappingURL=chunk-67JOMNSD.js.map
127
+ //# sourceMappingURL=chunk-67JOMNSD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/views/AnalyticsAssistant.tsx","../src/views/AnalyticsModule.tsx"],"names":["React","useTranslation","jsx"],"mappings":"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","file":"chunk-67JOMNSD.js","sourcesContent":["import React from 'react'\nimport { useTranslation } from '@fayz-ai/core'\nimport { AssistantInvite } from '@fayz-ai/admin'\nimport { useAnalyticsConfig } from './AnalyticsSurface'\n\n// ---------------------------------------------------------------------------\n// The assistant's offer on Analytics.\n//\n// It belongs to the MODULE, not to the overview: a question about a report is\n// asked while reading that report, and a composer that only exists on the home\n// screen makes the user navigate away from the number to ask about it.\n// ---------------------------------------------------------------------------\n\nexport function AnalyticsAssistant() {\n const { assistant } = useAnalyticsConfig()\n const t = useTranslation()\n\n // Prompts the screen offers. App overrides win whole; otherwise the plugin's\n // own three, which are business questions and not report names on purpose.\n const suggestions = React.useMemo(\n () => assistant?.suggestions ?? [0, 1, 2].map((i) => ({ label: t(`reports.assistant.suggestion.${i}`) })),\n [assistant?.suggestions, t],\n )\n\n if (!assistant) return null\n\n return (\n <AssistantInvite\n // One id across the module: the same invitation followed from the grid to\n // a report, which is what lets the bar stay put instead of folding back\n // into the FAB and unrolling again on every internal navigation.\n id=\"analytics\"\n auto={assistant.auto ?? false}\n role={assistant.role ?? 'growth'}\n persona={assistant.persona ?? t('reports.assistant.persona')}\n headline={assistant.headline ?? t('reports.assistant.headline')}\n pitch={assistant.pitch ?? t('reports.assistant.pitch')}\n description={assistant.description ?? t('reports.assistant.description')}\n placeholder={assistant.placeholder ?? t('reports.assistant.placeholder')}\n suggestions={suggestions}\n />\n )\n}\n","import React from 'react'\nimport { ModulePage, useModuleLayout, type ModuleNavItem, type NavigationItem } from '@fayz-ai/ui'\nimport { useTranslation } from '@fayz-ai/core'\nimport type { AnalyticsCardDef } from '@fayz-ai/core'\nimport {\n useAnalyticsConfig, useAnalyticsCards, navigateAnalytics,\n ANALYTICS_BASE, REPORTS_BASE, SETTINGS_ROUTE,\n} from './AnalyticsSurface'\nimport { AnalyticsAssistant } from './AnalyticsAssistant'\n\n// ---------------------------------------------------------------------------\n// Analytics is ONE entry in the app nav, not two.\n//\n// \"Analytics\" and \"Reports\" were two top-level links to the same engine, which\n// made the user pick a door before knowing what was behind it. There is one\n// door now; inside it, the report groups (a card's `category`) and the reports\n// themselves ARE the navigation.\n//\n// Essa navegação mora DENTRO DA PÁGINA, em todo layout: a coluna própria do\n// ModulePage, abaixo da faixa que atravessa o cartão.\n//\n// Ela tomava o rail do aplicativo quando havia um (o mesmo que /settings fazia).\n// Abrir Relatórios apagava o menu do produto e punha trinta relatórios no lugar:\n// quem entrou para ver o ticket médio perdia de vista o Financeiro, a Agenda e\n// tudo o mais, e sair de lá era um \"← Voltar\". Olhar um relatório não é sair do\n// aplicativo. O rail fica; a coluna da direita é que muda.\n//\n// Charts are not re-implemented here: AnalyticsCard renders through the same\n// ChartWidget the dashboard uses.\n// ---------------------------------------------------------------------------\n\nexport type AnalyticsView = 'overview' | 'reports' | 'report' | 'live' | 'settings'\n\nexport interface AnalyticsModuleProps {\n view: AnalyticsView\n /** Route param when `view === 'report'` — marks the active entry. */\n cardId?: string\n children?: React.ReactNode\n}\n\nconst reportRoute = (cardId: string) => `${REPORTS_BASE}/${encodeURIComponent(cardId)}`\n\n/** Cards by category, categories A→Z, reports A→Z inside each. */\nfunction groupCards(cards: AnalyticsCardDef[]): [string, AnalyticsCardDef[]][] {\n const groups = new Map<string, AnalyticsCardDef[]>()\n for (const card of cards) {\n const list = groups.get(card.category)\n if (list) list.push(card)\n else groups.set(card.category, [card])\n }\n return [...groups.entries()]\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([category, list]) => [category, [...list].sort((a, b) => a.name.localeCompare(b.name))])\n}\n\n/** The module's whole tree, in the order both renderers walk it. */\nfunction useAnalyticsTree() {\n const t = useTranslation()\n const { labels, showLiveView } = useAnalyticsConfig()\n const cards = useAnalyticsCards()\n\n return React.useMemo(() => ({\n title: labels.analytics,\n // Only the overview. The searchable catalogue (/analytics/reports) is still\n // routed — bookmarks and the legacy /reports link resolve — but it is not\n // listed: the tree below already IS the catalogue, and a link named \"all\n // reports\" sitting above the reports reads as a different, fuller list.\n fixed: [\n { id: 'overview', label: labels.overview ?? t('reports.overview'), icon: 'BarChart3', route: ANALYTICS_BASE, view: 'overview' as const },\n ],\n groups: groupCards(cards),\n live: showLiveView\n ? { id: 'live', label: labels.liveView, icon: 'Radio', route: `${ANALYTICS_BASE}/live`, view: 'live' as const }\n : null,\n // Depois dos relatórios, não antes: é a tela que fala SOBRE a lista, e uma\n // engrenagem no topo empurra para baixo o que a pessoa veio ver.\n settings: {\n id: 'settings',\n label: labels.settings ?? t('reports.settings.nav'),\n icon: 'SlidersHorizontal',\n route: SETTINGS_ROUTE,\n view: 'settings' as const,\n },\n }), [labels, showLiveView, cards, t])\n}\n\nexport function AnalyticsModule({ view, cardId, children }: AnalyticsModuleProps) {\n const t = useTranslation()\n const { labels } = useAnalyticsConfig()\n const variant = useModuleLayout()\n const tree = useAnalyticsTree()\n const activeId = cardId ? decodeURIComponent(cardId) : undefined\n // Does a shell rail exist to take over? The SHELL answers — it is the only\n // thing that knows. Asking the module-nav context instead meant reading its\n // default ('rail' = \"draw your own\") whenever the provider was out of the\n // tree, and the app rail plus this module's own column made exactly the two\n // menus the takeover exists to remove. The context is the fallback, for a\n // module mounted outside an AdminShell at all.\n // --- the in-page rail, in every layout ----------------------------------\n const nav = React.useMemo<ModuleNavItem[]>(() => {\n return [\n ...tree.fixed.map((f) => ({\n id: f.id, label: f.label, icon: f.icon, active: view === f.view, onClick: () => navigateAnalytics(f.route),\n })),\n ...tree.groups.map(([category, list]): ModuleNavItem => ({\n id: `group:${category}`,\n label: category,\n children: list.map((card) => ({\n id: card.id,\n label: card.name,\n active: view === 'report' && card.id === activeId,\n onClick: () => navigateAnalytics(reportRoute(card.id)),\n })),\n })),\n ...(tree.live ? [{\n id: tree.live.id, label: tree.live.label, icon: tree.live.icon,\n active: view === 'live', onClick: () => navigateAnalytics(tree.live!.route),\n }] : []),\n {\n id: tree.settings.id, label: tree.settings.label, icon: tree.settings.icon,\n active: view === 'settings', onClick: () => navigateAnalytics(tree.settings.route),\n },\n ]\n }, [tree, view, activeId])\n\n return (\n <ModulePage\n title={tree.title}\n nav={nav}\n // `rail` em todo layout: a coluna é desta página, não do aplicativo.\n navVariant=\"rail\"\n // A busca que a rail do aplicativo carregava vem junto — sem ela, trinta\n // relatórios em quatro grupos são rolagem, não navegação.\n navFilterPlaceholder={labels.searchReports ?? t('reports.search')}\n showHeader={false}\n viewKey={view === 'report' ? `report:${activeId}` : view}\n >\n {/* Above the page body and outside its flow: the invite occupies no\n layout, it only measures where the content column is. */}\n <AnalyticsAssistant />\n {/* Tail room for the docked composer, which floats over the column —\n without it the last row of every analytics page ends underneath it. */}\n <div className=\"md:pb-16\">{children}</div>\n </ModulePage>\n )\n}\n"]}
@@ -0,0 +1,145 @@
1
+ import { useAnalyticsConfig, useAnalyticsCards, useLastViewed, markViewed, navigateAnalytics, REPORTS_BASE } from './chunk-YVLQV4N7.js';
2
+ import React from 'react';
3
+ import { User, Search } from 'lucide-react';
4
+ import { Card, Input, FilterChip, DataTable } from '@fayz-ai/ui';
5
+ import { jsxs, jsx } from 'react/jsx-runtime';
6
+
7
+ function formatViewed(iso, locale, never) {
8
+ if (!iso) return never;
9
+ const d = new Date(iso);
10
+ if (Number.isNaN(d.getTime())) return never;
11
+ return d.toLocaleDateString(locale, { day: "numeric", month: "short", year: "numeric" });
12
+ }
13
+ function ReportsListPage() {
14
+ const { labels, currency, showHeader } = useAnalyticsConfig();
15
+ const cards = useAnalyticsCards();
16
+ const lastViewed = useLastViewed();
17
+ const [search, setSearch] = React.useState("");
18
+ const [category, setCategory] = React.useState();
19
+ const [author, setAuthor] = React.useState();
20
+ const categoryOptions = React.useMemo(
21
+ () => [...new Set(cards.map((c) => c.category))].sort((a, b) => a.localeCompare(b)).map((c) => ({ value: c, label: c })),
22
+ [cards]
23
+ );
24
+ const authorOptions = React.useMemo(
25
+ () => [...new Set(cards.map((c) => c.createdBy ?? "\u2014"))].sort((a, b) => a.localeCompare(b)).map((a) => ({ value: a, label: a })),
26
+ [cards]
27
+ );
28
+ const rows = React.useMemo(() => {
29
+ const q = search.trim().toLowerCase();
30
+ return cards.filter((card) => {
31
+ if (category && card.category !== category) return false;
32
+ if (author && (card.createdBy ?? "\u2014") !== author) return false;
33
+ if (!q) return true;
34
+ return card.name.toLowerCase().includes(q) || card.description?.toLowerCase().includes(q) || card.category.toLowerCase().includes(q);
35
+ });
36
+ }, [cards, search, category, author]);
37
+ const columns = React.useMemo(() => [
38
+ {
39
+ id: "name",
40
+ accessorFn: (card) => card.name,
41
+ header: labels.name,
42
+ cell: ({ row }) => /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
43
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: row.original.name }),
44
+ 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,
45
+ row.original.description ? /* @__PURE__ */ jsx("p", { className: "mt-0.5 line-clamp-1 text-muted-foreground sm:hidden", children: row.original.description }) : null
46
+ ] })
47
+ },
48
+ {
49
+ id: "category",
50
+ accessorFn: (card) => card.category,
51
+ header: labels.category,
52
+ cell: ({ getValue }) => /* @__PURE__ */ jsx("span", { className: "rounded-md bg-muted px-2 py-0.5", children: String(getValue() ?? "") })
53
+ },
54
+ {
55
+ id: "lastViewed",
56
+ // Sorts on the timestamp, renders the date. Never-opened reports carry
57
+ // undefined so they sink to the bottom in BOTH directions rather than
58
+ // topping "most recent" as an epoch-zero date.
59
+ accessorFn: (card) => {
60
+ const iso = lastViewed[card.id];
61
+ return iso ? new Date(iso).getTime() : void 0;
62
+ },
63
+ sortUndefined: 1,
64
+ header: labels.lastViewed,
65
+ cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: formatViewed(lastViewed[row.original.id], currency.locale, labels.never) })
66
+ },
67
+ {
68
+ id: "createdBy",
69
+ accessorFn: (card) => card.createdBy ?? "\u2014",
70
+ header: labels.createdBy,
71
+ cell: ({ getValue }) => /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5 text-muted-foreground", children: [
72
+ /* @__PURE__ */ jsx(User, { className: "h-3 w-3" }),
73
+ String(getValue() ?? "\u2014")
74
+ ] })
75
+ }
76
+ ], [labels, currency.locale, lastViewed]);
77
+ const open = (card) => {
78
+ markViewed(card.id);
79
+ navigateAnalytics(`${REPORTS_BASE}/${encodeURIComponent(card.id)}`);
80
+ };
81
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
82
+ showHeader ? /* @__PURE__ */ jsxs("div", { children: [
83
+ /* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold tracking-tight", children: labels.reports }),
84
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: labels.reportsSubtitle })
85
+ ] }) : null,
86
+ /* @__PURE__ */ jsxs(Card, { className: "overflow-hidden", children: [
87
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 border-b border-border px-3 py-2", children: [
88
+ /* @__PURE__ */ jsx("div", { className: "min-w-[10rem] flex-1", children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
89
+ /* @__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" }),
90
+ /* @__PURE__ */ jsx(
91
+ Input,
92
+ {
93
+ value: search,
94
+ onChange: (e) => setSearch(e.target.value),
95
+ placeholder: labels.searchReports,
96
+ className: "h-auto rounded-none border-0 bg-transparent py-1.5 pl-8 pr-3 shadow-none focus-visible:ring-0 focus-visible:ring-offset-0"
97
+ }
98
+ )
99
+ ] }) }),
100
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 flex-wrap items-center gap-1.5", children: [
101
+ /* @__PURE__ */ jsx(
102
+ FilterChip,
103
+ {
104
+ label: labels.category,
105
+ allLabel: labels.allCategories,
106
+ options: categoryOptions,
107
+ value: category,
108
+ onChange: setCategory
109
+ }
110
+ ),
111
+ /* @__PURE__ */ jsx(
112
+ FilterChip,
113
+ {
114
+ label: labels.createdBy,
115
+ allLabel: labels.allCreatedBy,
116
+ options: authorOptions,
117
+ value: author,
118
+ onChange: setAuthor
119
+ }
120
+ )
121
+ ] })
122
+ ] }),
123
+ /* @__PURE__ */ jsx(
124
+ DataTable,
125
+ {
126
+ columns,
127
+ data: rows,
128
+ variant: "bare",
129
+ compact: true,
130
+ onRowClick: open,
131
+ emptyMessage: labels.noResults
132
+ }
133
+ ),
134
+ /* @__PURE__ */ jsxs("div", { className: "border-t border-border px-3 py-2 text-xs text-muted-foreground", children: [
135
+ rows.length,
136
+ " ",
137
+ labels.results
138
+ ] })
139
+ ] })
140
+ ] });
141
+ }
142
+
143
+ export { ReportsListPage };
144
+ //# sourceMappingURL=chunk-DP7S37Y2.js.map
145
+ //# sourceMappingURL=chunk-DP7S37Y2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/views/ReportsListPage.tsx"],"names":[],"mappings":"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","file":"chunk-DP7S37Y2.js","sourcesContent":["import React from 'react'\nimport { Search, User } from 'lucide-react'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport {\n Card, DataTable, FilterChip, Input, type FilterChipOption,\n} from '@fayz-ai/ui'\nimport type { AnalyticsCardDef } from '@fayz-ai/core'\nimport { useAnalyticsConfig, useAnalyticsCards, navigateAnalytics, REPORTS_BASE } from './AnalyticsSurface'\nimport { useLastViewed, markViewed } from '../lib/lastViewed'\n\n// ---------------------------------------------------------------------------\n// The one list where a user finds what they want.\n//\n// This replaces the old category-card hub. Cards-in-a-grid look richer but stop\n// working past about twenty reports: you cannot sort them, cannot see when you\n// last opened one, and finding \"sales by vendor\" means reading every tile. A\n// table sorts, scans and filters — which is what a catalogue is for.\n//\n// It renders through the shared DataTable rather than a hand-rolled <table>.\n// The first version copied DataTable's classes by hand and drifted from it\n// immediately — wrong header tint, wrong corner radius — because a copy has to\n// be re-copied every time the original moves. `variant=\"bare\"` exists for this\n// exact shape: a table under a search toolbar, inside a card that already owns\n// the border and radius.\n// ---------------------------------------------------------------------------\n\nfunction formatViewed(iso: string | undefined, locale: string, never: string): string {\n if (!iso) return never\n const d = new Date(iso)\n if (Number.isNaN(d.getTime())) return never\n return d.toLocaleDateString(locale, { day: 'numeric', month: 'short', year: 'numeric' })\n}\n\nexport function ReportsListPage() {\n const { labels, currency, showHeader } = useAnalyticsConfig()\n const cards = useAnalyticsCards()\n const lastViewed = useLastViewed()\n\n const [search, setSearch] = React.useState('')\n // Single-select, like every other facet in the product (see FilterChip):\n // the chip states what is filtering right now, and picking \"all\" clears it.\n const [category, setCategory] = React.useState<string | undefined>()\n const [author, setAuthor] = React.useState<string | undefined>()\n\n const categoryOptions = React.useMemo<FilterChipOption[]>(\n () => [...new Set(cards.map((c) => c.category))]\n .sort((a, b) => a.localeCompare(b))\n .map((c) => ({ value: c, label: c })),\n [cards],\n )\n const authorOptions = React.useMemo<FilterChipOption[]>(\n () => [...new Set(cards.map((c) => c.createdBy ?? '—'))]\n .sort((a, b) => a.localeCompare(b))\n .map((a) => ({ value: a, label: a })),\n [cards],\n )\n\n const rows = React.useMemo(() => {\n const q = search.trim().toLowerCase()\n return cards.filter((card) => {\n if (category && card.category !== category) return false\n if (author && (card.createdBy ?? '—') !== author) return false\n if (!q) return true\n // Description is searched too: users look for \"no-show\" long before they\n // remember a report is filed under \"Cancellations\".\n return card.name.toLowerCase().includes(q)\n || card.description?.toLowerCase().includes(q)\n || card.category.toLowerCase().includes(q)\n })\n }, [cards, search, category, author])\n\n const columns = React.useMemo<ColumnDef<AnalyticsCardDef, unknown>[]>(() => [\n {\n id: 'name',\n accessorFn: (card) => card.name,\n header: labels.name,\n cell: ({ row }) => (\n <div className=\"min-w-0\">\n <span className=\"font-medium\">{row.original.name}</span>\n {row.original.available === false ? (\n <span className=\"ml-2 rounded-full border px-1.5 py-0.5 text-[10px] text-muted-foreground\">\n {labels.unavailableTitle}\n </span>\n ) : null}\n {row.original.description ? (\n <p className=\"mt-0.5 line-clamp-1 text-muted-foreground sm:hidden\">{row.original.description}</p>\n ) : null}\n </div>\n ),\n },\n {\n id: 'category',\n accessorFn: (card) => card.category,\n header: labels.category,\n cell: ({ getValue }) => (\n <span className=\"rounded-md bg-muted px-2 py-0.5\">{String(getValue() ?? '')}</span>\n ),\n },\n {\n id: 'lastViewed',\n // Sorts on the timestamp, renders the date. Never-opened reports carry\n // undefined so they sink to the bottom in BOTH directions rather than\n // topping \"most recent\" as an epoch-zero date.\n accessorFn: (card) => {\n const iso = lastViewed[card.id]\n return iso ? new Date(iso).getTime() : undefined\n },\n sortUndefined: 1,\n header: labels.lastViewed,\n cell: ({ row }) => (\n <span className=\"text-muted-foreground\">\n {formatViewed(lastViewed[row.original.id], currency.locale, labels.never)}\n </span>\n ),\n },\n {\n id: 'createdBy',\n accessorFn: (card) => card.createdBy ?? '—',\n header: labels.createdBy,\n cell: ({ getValue }) => (\n <span className=\"inline-flex items-center gap-1.5 text-muted-foreground\">\n <User className=\"h-3 w-3\" />\n {String(getValue() ?? '—')}\n </span>\n ),\n },\n ], [labels, currency.locale, lastViewed])\n\n const open = (card: AnalyticsCardDef) => {\n markViewed(card.id)\n navigateAnalytics(`${REPORTS_BASE}/${encodeURIComponent(card.id)}`)\n }\n\n return (\n <div className=\"space-y-4\">\n {showHeader ? (\n <div>\n <h1 className=\"text-2xl font-semibold tracking-tight\">{labels.reports}</h1>\n <p className=\"mt-1 text-sm text-muted-foreground\">{labels.reportsSubtitle}</p>\n </div>\n ) : null}\n\n {/* Same composition as CrudListView: search, tools and the table are ONE\n surface, because they act on the same rows. The Card owns the border\n and radius, so the table inside goes `bare`. */}\n <Card className=\"overflow-hidden\">\n <div className=\"flex flex-wrap items-center gap-2 border-b border-border px-3 py-2\">\n <div className=\"min-w-[10rem] flex-1\">\n <div className=\"relative\">\n <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\" />\n {/* Borderless, exactly as CrudListView's search box: the card\n already draws the boundary, and a second box inside it would\n be a frame inside a frame. */}\n <Input\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n placeholder={labels.searchReports}\n className=\"h-auto rounded-none border-0 bg-transparent py-1.5 pl-8 pr-3 shadow-none focus-visible:ring-0 focus-visible:ring-offset-0\"\n />\n </div>\n </div>\n\n <div className=\"flex shrink-0 flex-wrap items-center gap-1.5\">\n <FilterChip\n label={labels.category}\n allLabel={labels.allCategories}\n options={categoryOptions}\n value={category}\n onChange={setCategory}\n />\n <FilterChip\n label={labels.createdBy}\n allLabel={labels.allCreatedBy}\n options={authorOptions}\n value={author}\n onChange={setAuthor}\n />\n </div>\n </div>\n\n <DataTable\n columns={columns}\n data={rows}\n variant=\"bare\"\n compact\n onRowClick={open}\n emptyMessage={labels.noResults}\n />\n\n <div className=\"border-t border-border px-3 py-2 text-xs text-muted-foreground\">\n {rows.length} {labels.results}\n </div>\n </Card>\n </div>\n )\n}\n"]}