@happyvertical/smrt-reports 0.55.24 → 0.56.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.
Files changed (91) hide show
  1. package/AGENTS.md +26 -3
  2. package/README.md +11 -2
  3. package/dist/index.d.ts +4 -1
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +476 -109
  6. package/dist/index.js.map +1 -1
  7. package/dist/manifest.json +174 -5
  8. package/dist/materialized-host.d.ts +72 -0
  9. package/dist/materialized-host.d.ts.map +1 -0
  10. package/dist/playground.d.ts +2 -0
  11. package/dist/playground.d.ts.map +1 -0
  12. package/dist/playground.js +154 -0
  13. package/dist/playground.js.map +1 -0
  14. package/dist/recipes.d.ts +55 -0
  15. package/dist/recipes.d.ts.map +1 -1
  16. package/dist/runtime-report.d.ts +9 -0
  17. package/dist/runtime-report.d.ts.map +1 -1
  18. package/dist/runtime-server.d.ts +77 -0
  19. package/dist/runtime-server.d.ts.map +1 -0
  20. package/dist/runtime-spec.d.ts +2 -1
  21. package/dist/runtime-spec.d.ts.map +1 -1
  22. package/dist/smrt-knowledge.json +203 -11
  23. package/dist/state.js +1 -1
  24. package/dist/state.js.map +1 -1
  25. package/dist/svelte/MaterializedReportTable.svelte +104 -0
  26. package/dist/svelte/MaterializedReportTable.svelte.d.ts +14 -0
  27. package/dist/svelte/MaterializedReportTable.svelte.d.ts.map +1 -0
  28. package/dist/svelte/ReportConfirmation.svelte +99 -0
  29. package/dist/svelte/ReportConfirmation.svelte.d.ts +15 -0
  30. package/dist/svelte/ReportConfirmation.svelte.d.ts.map +1 -0
  31. package/dist/svelte/ReportDetail.svelte +69 -0
  32. package/dist/svelte/ReportDetail.svelte.d.ts +11 -0
  33. package/dist/svelte/ReportDetail.svelte.d.ts.map +1 -0
  34. package/dist/svelte/ReportPicker.svelte +73 -0
  35. package/dist/svelte/ReportPicker.svelte.d.ts +16 -0
  36. package/dist/svelte/ReportPicker.svelte.d.ts.map +1 -0
  37. package/dist/svelte/ReportResult.svelte +69 -0
  38. package/dist/svelte/ReportResult.svelte.d.ts +10 -0
  39. package/dist/svelte/ReportResult.svelte.d.ts.map +1 -0
  40. package/dist/svelte/RuntimeReportChartWidget.svelte +20 -0
  41. package/dist/svelte/RuntimeReportChartWidget.svelte.d.ts +11 -0
  42. package/dist/svelte/RuntimeReportChartWidget.svelte.d.ts.map +1 -0
  43. package/dist/svelte/RuntimeReportDetailRoute.svelte +67 -0
  44. package/dist/svelte/RuntimeReportDetailRoute.svelte.d.ts +21 -0
  45. package/dist/svelte/RuntimeReportDetailRoute.svelte.d.ts.map +1 -0
  46. package/dist/svelte/RuntimeReportToolResult.svelte +26 -0
  47. package/dist/svelte/RuntimeReportToolResult.svelte.d.ts +8 -0
  48. package/dist/svelte/RuntimeReportToolResult.svelte.d.ts.map +1 -0
  49. package/dist/svelte/RuntimeReportWidgetOptions.svelte +58 -0
  50. package/dist/svelte/RuntimeReportWidgetOptions.svelte.d.ts +14 -0
  51. package/dist/svelte/RuntimeReportWidgetOptions.svelte.d.ts.map +1 -0
  52. package/dist/svelte/RuntimeReportsRoute.svelte +67 -0
  53. package/dist/svelte/RuntimeReportsRoute.svelte.d.ts +19 -0
  54. package/dist/svelte/RuntimeReportsRoute.svelte.d.ts.map +1 -0
  55. package/dist/svelte/SavedReports.svelte +114 -0
  56. package/dist/svelte/SavedReports.svelte.d.ts +11 -0
  57. package/dist/svelte/SavedReports.svelte.d.ts.map +1 -0
  58. package/dist/svelte/browser-client.d.ts +10 -0
  59. package/dist/svelte/browser-client.d.ts.map +1 -0
  60. package/dist/svelte/browser-client.js +16 -0
  61. package/dist/svelte/client.d.ts +15 -0
  62. package/dist/svelte/client.d.ts.map +1 -0
  63. package/dist/svelte/client.js +179 -0
  64. package/dist/svelte/index.d.ts +17 -0
  65. package/dist/svelte/index.d.ts.map +1 -0
  66. package/dist/svelte/index.js +14 -0
  67. package/dist/svelte/materialized.d.ts +19 -0
  68. package/dist/svelte/materialized.d.ts.map +1 -0
  69. package/dist/svelte/materialized.js +1 -0
  70. package/dist/svelte/messages.d.ts +72 -0
  71. package/dist/svelte/messages.d.ts.map +1 -0
  72. package/dist/svelte/messages.js +72 -0
  73. package/dist/svelte/playground.d.ts +132 -0
  74. package/dist/svelte/playground.d.ts.map +1 -0
  75. package/dist/svelte/playground.js +62 -0
  76. package/dist/svelte/preview-data.d.ts +7 -0
  77. package/dist/svelte/preview-data.d.ts.map +1 -0
  78. package/dist/svelte/preview-data.js +59 -0
  79. package/dist/svelte/result.d.ts +8 -0
  80. package/dist/svelte/result.d.ts.map +1 -0
  81. package/dist/svelte/result.js +99 -0
  82. package/dist/svelte/spec.d.ts +5 -0
  83. package/dist/svelte/spec.d.ts.map +1 -0
  84. package/dist/svelte/spec.js +282 -0
  85. package/dist/svelte/tool-result.d.ts +11 -0
  86. package/dist/svelte/tool-result.d.ts.map +1 -0
  87. package/dist/svelte/tool-result.js +19 -0
  88. package/dist/svelte/types.d.ts +38 -0
  89. package/dist/svelte/types.d.ts.map +1 -0
  90. package/dist/svelte/types.js +1 -0
  91. package/package.json +36 -12
@@ -0,0 +1,104 @@
1
+ <script lang="ts">
2
+ import { formatWidgetValue } from '@happyvertical/smrt-ui/charts/format';
3
+ import { DataTable, type DataTableColumn } from '@happyvertical/smrt-ui/data';
4
+ import { Button } from '@happyvertical/smrt-ui/ui';
5
+ import type { ReportDataTableColumn } from '../adapter.js';
6
+ import type {
7
+ MaterializedReportDrilldown,
8
+ MaterializedReportPage,
9
+ } from './materialized.js';
10
+
11
+ export interface Props {
12
+ /** Server-projected descriptor and bounded row page. No client tenant selector exists. */
13
+ page: MaterializedReportPage;
14
+ locale?: string;
15
+ onDrilldown?: MaterializedReportDrilldown;
16
+ /** The application route owns URL navigation for a changed server page. */
17
+ onPageChange?: (page: number) => void | Promise<void>;
18
+ caption?: string;
19
+ }
20
+
21
+ let {
22
+ page,
23
+ locale = 'en',
24
+ onDrilldown,
25
+ onPageChange,
26
+ caption = 'Materialized report',
27
+ }: Props = $props();
28
+
29
+ const validCurrency = (value: unknown): value is string =>
30
+ typeof value === 'string' && /^[A-Za-z]{3}$/.test(value);
31
+
32
+ function columns(): DataTableColumn<Record<string, unknown>>[] {
33
+ return page.descriptor.dataTable.columns.map(
34
+ (column: ReportDataTableColumn) => ({
35
+ id: column.id,
36
+ label: column.label,
37
+ accessor: column.accessor,
38
+ sortable: column.sortable,
39
+ filterable: column.filterable,
40
+ align: column.align,
41
+ role: column.role,
42
+ responsive: column.responsive,
43
+ }),
44
+ );
45
+ }
46
+
47
+ function formattedValue(
48
+ columnId: string,
49
+ value: unknown,
50
+ row: Record<string, unknown>,
51
+ ): string {
52
+ const column = page.descriptor.dataTable.columns.find(
53
+ (candidate) => candidate.id === columnId,
54
+ );
55
+ if (!column) return '–';
56
+ if (column.valueFormat === 'money') {
57
+ if (!Number.isSafeInteger(value) || !validCurrency(row.currency))
58
+ return '–';
59
+ return formatWidgetValue(value, {
60
+ format: 'money',
61
+ currency: row.currency,
62
+ locale,
63
+ });
64
+ }
65
+ return value == null ? '–' : String(value);
66
+ }
67
+
68
+ async function drilldown(id: unknown, columnId: string): Promise<void> {
69
+ if (typeof id !== 'string' || !id || !onDrilldown) return;
70
+ if (!page.drilldownColumnIds?.includes(columnId)) return;
71
+ await onDrilldown({ id, columnId });
72
+ }
73
+ </script>
74
+
75
+ {#snippet cell({ column, row, value }: {
76
+ column: DataTableColumn<Record<string, unknown>>;
77
+ row: Record<string, unknown>;
78
+ value: unknown;
79
+ })}
80
+ {#if onDrilldown && page.drilldownColumnIds?.includes(column.id)}
81
+ <Button
82
+ variant="ghost"
83
+ aria-label={`Inspect ${column.label}`}
84
+ onclick={() => drilldown(row[page.descriptor.identityField], column.id)}
85
+ >{formattedValue(column.id, value, row)}</Button>
86
+ {:else}
87
+ {formattedValue(column.id, value, row)}
88
+ {/if}
89
+ {/snippet}
90
+
91
+ <div data-smrt-component="MaterializedReportTable">
92
+ <DataTable
93
+ data={page.rows}
94
+ columns={columns()}
95
+ rowKey={page.descriptor.identityField}
96
+ manualPagination
97
+ totalRows={page.totalRows}
98
+ page={Math.floor(page.offset / page.limit) + 1}
99
+ pageSize={page.limit}
100
+ {caption}
101
+ {onPageChange}
102
+ {cell}
103
+ />
104
+ </div>
@@ -0,0 +1,14 @@
1
+ import type { MaterializedReportDrilldown, MaterializedReportPage } from './materialized.js';
2
+ export interface Props {
3
+ /** Server-projected descriptor and bounded row page. No client tenant selector exists. */
4
+ page: MaterializedReportPage;
5
+ locale?: string;
6
+ onDrilldown?: MaterializedReportDrilldown;
7
+ /** The application route owns URL navigation for a changed server page. */
8
+ onPageChange?: (page: number) => void | Promise<void>;
9
+ caption?: string;
10
+ }
11
+ declare const MaterializedReportTable: import("svelte").Component<Props, {}, "">;
12
+ type MaterializedReportTable = ReturnType<typeof MaterializedReportTable>;
13
+ export default MaterializedReportTable;
14
+ //# sourceMappingURL=MaterializedReportTable.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MaterializedReportTable.svelte.d.ts","sourceRoot":"","sources":["../../src/svelte/MaterializedReportTable.svelte.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,2BAA2B,EAC3B,sBAAsB,EACvB,MAAM,mBAAmB,CAAC;AAG3B,MAAM,WAAW,KAAK;IACpB,0FAA0F;IAC1F,IAAI,EAAE,sBAAsB,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,2BAA2B,CAAC;IAC1C,2EAA2E;IAC3E,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAiFD,QAAA,MAAM,uBAAuB,2CAAwC,CAAC;AACtE,KAAK,uBAAuB,GAAG,UAAU,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAC1E,eAAe,uBAAuB,CAAC"}
@@ -0,0 +1,99 @@
1
+ <script lang="ts">
2
+ import { useI18n } from '@happyvertical/smrt-ui/i18n';
3
+ import { Button } from '@happyvertical/smrt-ui/ui';
4
+ import { M } from './messages.js';
5
+ import ReportResult from './ReportResult.svelte';
6
+ import { reportError, validReportProposal } from './result.js';
7
+ import type { RuntimeReportProposal, SavedRuntimeReport } from './types.js';
8
+
9
+ export interface Props {
10
+ proposal: RuntimeReportProposal;
11
+ /** Posts the exact displayed spec and hash to the authenticated confirm route. */
12
+ confirm: (proposal: RuntimeReportProposal) => Promise<SavedRuntimeReport>;
13
+ reportHref?: (id: string) => string;
14
+ ondecline?: () => void;
15
+ onsaved?: (report: SavedRuntimeReport) => void;
16
+ locale?: string;
17
+ currency?: string;
18
+ }
19
+ let {
20
+ proposal,
21
+ confirm,
22
+ reportHref = (id) => `/reports/${encodeURIComponent(id)}`,
23
+ ondecline,
24
+ onsaved,
25
+ locale = 'en',
26
+ currency = 'USD',
27
+ }: Props = $props();
28
+ const { t } = useI18n();
29
+ let status = $state<'idle' | 'saving' | 'declined' | 'saved'>('idle');
30
+ let saved = $state<SavedRuntimeReport | null>(null);
31
+ let error = $state<ReturnType<typeof reportError> | null>(null);
32
+ let epoch = 0;
33
+ $effect(() => {
34
+ proposal;
35
+ confirm;
36
+ ++epoch;
37
+ status = 'idle';
38
+ saved = null;
39
+ error = null;
40
+ return () => {
41
+ ++epoch;
42
+ };
43
+ });
44
+ const valid = $derived(validReportProposal(proposal));
45
+ const label = (field: string) => field;
46
+ async function approve(event: MouseEvent) {
47
+ // Approval is an immediate local human action, never a model-callable tool.
48
+ if (!event.isTrusted || status !== 'idle' || !valid) return;
49
+ const current = epoch;
50
+ const specHash = proposal.specHash;
51
+ status = 'saving';
52
+ error = null;
53
+ try {
54
+ const result = await confirm({ ...proposal, specHash });
55
+ if (current !== epoch) return;
56
+ saved = result;
57
+ status = 'saved';
58
+ onsaved?.(result);
59
+ } catch (e) {
60
+ if (current === epoch) {
61
+ status = 'idle';
62
+ error = reportError(e);
63
+ }
64
+ }
65
+ }
66
+ function decline() {
67
+ if (status !== 'idle') return;
68
+ status = 'declined';
69
+ ondecline?.();
70
+ }
71
+ </script>
72
+
73
+ <section data-smrt-component="ReportConfirmation" aria-label={t(M['reports.confirm_title'])}>
74
+ <h2>{valid ? proposal.spec.title : t(M['reports.confirm_title'])}</h2>
75
+ {#if !valid}
76
+ <p role="alert">{t(M['reports.invalid'])}</p>
77
+ {:else if saved}
78
+ <p role="status">{t(M['reports.saved'])}</p>
79
+ <a href={reportHref(saved.id)}>{saved.title}</a>
80
+ {:else if status === 'declined'}
81
+ <p role="status">{t(M['reports.declined'])}</p>
82
+ {:else}
83
+ {#if proposal.spec.description}<p>{proposal.spec.description}</p>{/if}
84
+ <dl>
85
+ <dt>{t(M['reports.source'])}</dt><dd>{proposal.sourceLabel}</dd>
86
+ <dt>{t(M['reports.groupings'])}</dt>
87
+ <dd>{#each proposal.spec.dimensions as d (d.as)}<p>{label(d.field)}{#if d.bucket} · {t(`reports.bucket.${d.bucket}`)}{/if}</p>{:else}{t(M['reports.none'])}{/each}</dd>
88
+ <dt>{t(M['reports.measures'])}</dt>
89
+ <dd>{#each proposal.spec.measures as m (m.as)}<p>{t(`reports.aggregate.${m.fn}`, { field: m.field ? label(m.field) : t(M['reports.rows']) })}</p>{/each}</dd>
90
+ <dt>{t(M['reports.filters'])}</dt>
91
+ <dd>{#each proposal.spec.filters as f, i (i)}<p>{label(f.field)} {t(`reports.operator.${f.op}`)} {Array.isArray(f.value) ? f.value.join(', ') : String(f.value ?? '')}</p>{:else}{t(M['reports.none'])}{/each}</dd>
92
+ {#if proposal.spec.having.length}<dt>{t(M['reports.having'])}</dt><dd>{#each proposal.spec.having as h, i (i)}<p>{h.measure} {t(`reports.operator.${h.op}`)} {h.value}</p>{/each}</dd>{/if}
93
+ </dl>
94
+ <ReportResult result={proposal.preview} {locale} {currency} />
95
+ {#if error}<p role="alert">{t(M[error])}</p>{/if}
96
+ <Button disabled={status === 'saving'} onclick={approve}>{t(M[status === 'saving' ? 'reports.saving' : 'reports.approve'])}</Button>
97
+ <Button disabled={status === 'saving'} onclick={decline}>{t(M['reports.decline'])}</Button>
98
+ {/if}
99
+ </section>
@@ -0,0 +1,15 @@
1
+ import type { RuntimeReportProposal, SavedRuntimeReport } from './types.js';
2
+ export interface Props {
3
+ proposal: RuntimeReportProposal;
4
+ /** Posts the exact displayed spec and hash to the authenticated confirm route. */
5
+ confirm: (proposal: RuntimeReportProposal) => Promise<SavedRuntimeReport>;
6
+ reportHref?: (id: string) => string;
7
+ ondecline?: () => void;
8
+ onsaved?: (report: SavedRuntimeReport) => void;
9
+ locale?: string;
10
+ currency?: string;
11
+ }
12
+ declare const ReportConfirmation: import("svelte").Component<Props, {}, "">;
13
+ type ReportConfirmation = ReturnType<typeof ReportConfirmation>;
14
+ export default ReportConfirmation;
15
+ //# sourceMappingURL=ReportConfirmation.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReportConfirmation.svelte.d.ts","sourceRoot":"","sources":["../../src/svelte/ReportConfirmation.svelte.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAG5E,MAAM,WAAW,KAAK;IACpB,QAAQ,EAAE,qBAAqB,CAAC;IAChC,kFAAkF;IAClF,OAAO,EAAE,CAAC,QAAQ,EAAE,qBAAqB,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAC1E,UAAU,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AA6FD,QAAA,MAAM,kBAAkB,2CAAwC,CAAC;AACjE,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAChE,eAAe,kBAAkB,CAAC"}
@@ -0,0 +1,69 @@
1
+ <script lang="ts">
2
+ import { useI18n } from '@happyvertical/smrt-ui/i18n';
3
+ import { Button } from '@happyvertical/smrt-ui/ui';
4
+ import { M } from './messages.js';
5
+ import ReportResult from './ReportResult.svelte';
6
+ import { reportError, validReportResult } from './result.js';
7
+ import type {
8
+ RuntimeReportResult,
9
+ RuntimeReportsClient,
10
+ SavedRuntimeReport,
11
+ } from './types.js';
12
+
13
+ export interface Props {
14
+ id: string;
15
+ client?: RuntimeReportsClient;
16
+ locale?: string;
17
+ currency?: string;
18
+ }
19
+ let { id, client, locale = 'en', currency = 'USD' }: Props = $props();
20
+ const { t } = useI18n();
21
+ let report = $state<SavedRuntimeReport | null>(null);
22
+ let result = $state<RuntimeReportResult | null>(null);
23
+ let error = $state<ReturnType<typeof reportError> | null>(null);
24
+ let loading = $state(false);
25
+ let revision = $state(0);
26
+ $effect(() => {
27
+ const transport = client;
28
+ const reportId = id;
29
+ revision;
30
+ let active = true;
31
+ report = null;
32
+ result = null;
33
+ error = null;
34
+ loading = !!transport;
35
+ if (transport) {
36
+ void (async () => {
37
+ try {
38
+ const saved = await transport.get(reportId);
39
+ if (!active) return;
40
+ report = saved;
41
+ const value = await transport.run(reportId);
42
+ if (!active) return;
43
+ if (!validReportResult(value)) error = 'reports.invalid';
44
+ else result = value;
45
+ } catch (e) {
46
+ if (active) error = reportError(e);
47
+ } finally {
48
+ if (active) loading = false;
49
+ }
50
+ })();
51
+ }
52
+ return () => {
53
+ active = false;
54
+ };
55
+ });
56
+ </script>
57
+
58
+ <section data-smrt-component="ReportDetail" aria-label={t(M['reports.title'])}>
59
+ <h1>{report?.title ?? t(M['reports.title'])}</h1>
60
+ {#if !client}
61
+ <p>{t(M['reports.unavailable'])}</p>
62
+ {:else}
63
+ {#if report?.description}<p>{report.description}</p>{/if}
64
+ <Button disabled={loading} onclick={() => revision++}>{t(M['reports.rerun'])}</Button>
65
+ {#if loading}<p role="status">{t(M['reports.running'])}</p>{/if}
66
+ {#if error}<p role="alert">{t(M[error])}</p>{/if}
67
+ {#if result}<ReportResult {result} {locale} {currency} />{/if}
68
+ {/if}
69
+ </section>
@@ -0,0 +1,11 @@
1
+ import type { RuntimeReportsClient } from './types.js';
2
+ export interface Props {
3
+ id: string;
4
+ client?: RuntimeReportsClient;
5
+ locale?: string;
6
+ currency?: string;
7
+ }
8
+ declare const ReportDetail: import("svelte").Component<Props, {}, "">;
9
+ type ReportDetail = ReturnType<typeof ReportDetail>;
10
+ export default ReportDetail;
11
+ //# sourceMappingURL=ReportDetail.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReportDetail.svelte.d.ts","sourceRoot":"","sources":["../../src/svelte/ReportDetail.svelte.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAEV,oBAAoB,EAErB,MAAM,YAAY,CAAC;AAGpB,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AA+DD,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
@@ -0,0 +1,73 @@
1
+ <script lang="ts">
2
+ import { Select } from '@happyvertical/smrt-ui/forms';
3
+ import { useI18n } from '@happyvertical/smrt-ui/i18n';
4
+ import { M } from './messages.js';
5
+ import { reportError } from './result.js';
6
+ import type { RuntimeReportsClient, SavedRuntimeReport } from './types.js';
7
+
8
+ export interface Props {
9
+ client?: RuntimeReportsClient;
10
+ reportId?: string;
11
+ measure?: string;
12
+ dimension?: string;
13
+ onchange: (value: {
14
+ reportId: string;
15
+ measure: string;
16
+ dimension: string;
17
+ }) => void;
18
+ }
19
+ let {
20
+ client,
21
+ reportId = '',
22
+ measure = '',
23
+ dimension = '',
24
+ onchange,
25
+ }: Props = $props();
26
+ const { t } = useI18n();
27
+ let reports = $state<SavedRuntimeReport[]>([]);
28
+ let loading = $state(false);
29
+ let error = $state<ReturnType<typeof reportError> | null>(null);
30
+ const selected = $derived(reports.find((r) => r.id === reportId));
31
+ $effect(() => {
32
+ const transport = client;
33
+ let active = true;
34
+ reports = [];
35
+ error = null;
36
+ loading = !!transport;
37
+ if (transport)
38
+ void transport
39
+ .list({ onlyMine: false, includeArchived: false })
40
+ .then((rows) => {
41
+ if (active) reports = rows;
42
+ })
43
+ .catch((e) => {
44
+ if (active) error = reportError(e);
45
+ })
46
+ .finally(() => {
47
+ if (active) loading = false;
48
+ });
49
+ return () => {
50
+ active = false;
51
+ };
52
+ });
53
+ </script>
54
+
55
+ <div data-smrt-component="ReportPicker">
56
+ {#if !client}<p>{t(M['reports.unavailable'])}</p>
57
+ {:else}
58
+ {#if loading}<p role="status">{t(M['reports.loading'])}</p>{/if}
59
+ {#if error}<p role="alert">{t(M[error])}</p>{/if}
60
+ <Select aria-label={t(M['reports.select_report'])} value={reportId} disabled={loading || !!error} onchange={event => onchange({ reportId: event.currentTarget.value, measure: '', dimension: '' })}>
61
+ <option value="">{t(M['reports.choose_report'])}</option>
62
+ {#each reports as report (report.id)}<option value={report.id}>{report.title}</option>{/each}
63
+ </Select>
64
+ <Select aria-label={t(M['reports.select_measure'])} value={measure} disabled={!selected} onchange={event => onchange({ reportId, measure: event.currentTarget.value, dimension })}>
65
+ <option value="">{t(M['reports.choose_measure'])}</option>
66
+ {#each selected?.spec.measures ?? [] as m (m.as)}<option value={m.as}>{m.as}</option>{/each}
67
+ </Select>
68
+ <Select aria-label={t(M['reports.select_dimension'])} value={dimension} disabled={!selected} onchange={event => onchange({ reportId, measure, dimension: event.currentTarget.value })}>
69
+ <option value="">{t(M['reports.choose_dimension'])}</option>
70
+ {#each selected?.spec.dimensions ?? [] as d (d.as)}<option value={d.as}>{d.as}</option>{/each}
71
+ </Select>
72
+ {/if}
73
+ </div>
@@ -0,0 +1,16 @@
1
+ import type { RuntimeReportsClient } from './types.js';
2
+ export interface Props {
3
+ client?: RuntimeReportsClient;
4
+ reportId?: string;
5
+ measure?: string;
6
+ dimension?: string;
7
+ onchange: (value: {
8
+ reportId: string;
9
+ measure: string;
10
+ dimension: string;
11
+ }) => void;
12
+ }
13
+ declare const ReportPicker: import("svelte").Component<Props, {}, "">;
14
+ type ReportPicker = ReturnType<typeof ReportPicker>;
15
+ export default ReportPicker;
16
+ //# sourceMappingURL=ReportPicker.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReportPicker.svelte.d.ts","sourceRoot":"","sources":["../../src/svelte/ReportPicker.svelte.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,oBAAoB,EAAsB,MAAM,YAAY,CAAC;AAG3E,MAAM,WAAW,KAAK;IACpB,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,CAAC,KAAK,EAAE;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;KACnB,KAAK,IAAI,CAAC;CACZ;AAkED,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
@@ -0,0 +1,69 @@
1
+ <script lang="ts">
2
+ import { Chart } from '@happyvertical/smrt-ui/charts';
3
+ import { useI18n } from '@happyvertical/smrt-ui/i18n';
4
+ import { Button } from '@happyvertical/smrt-ui/ui';
5
+ import { M } from './messages.js';
6
+ import { reportCell, validReportResult } from './result.js';
7
+ import type { RuntimeReportResult } from './types.js';
8
+
9
+ export interface Props {
10
+ result: RuntimeReportResult;
11
+ locale?: string;
12
+ currency?: string;
13
+ }
14
+ let { result, locale = 'en', currency = 'USD' }: Props = $props();
15
+ const { t } = useI18n();
16
+ let table = $state(false);
17
+ const valid = $derived(validReportResult(result));
18
+ const measures = $derived(
19
+ valid
20
+ ? result.columns.filter((c) =>
21
+ (
22
+ result.chart.y ??
23
+ result.columns.filter((c) => c.role === 'measure').map((c) => c.id)
24
+ ).includes(c.id),
25
+ )
26
+ : [],
27
+ );
28
+ function cell(value: unknown, column: RuntimeReportResult['columns'][number]) {
29
+ return typeof value === 'boolean'
30
+ ? t(M[value ? 'reports.yes' : 'reports.no'])
31
+ : reportCell(value, column, locale, currency);
32
+ }
33
+ </script>
34
+
35
+ <div data-smrt-component="ReportResult">
36
+ {#if !valid}
37
+ <p role="alert">{t(M['reports.invalid'])}</p>
38
+ {:else if result.rows.length === 0}
39
+ <p>{t(M['reports.no_rows'])}</p>
40
+ {:else}
41
+ {#if result.chart.type !== 'table'}
42
+ <Button type="button" onclick={() => table = !table}>{t(M[table ? 'reports.chart' : 'reports.table'])}</Button>
43
+ {/if}
44
+ {#if table || result.chart.type === 'table'}
45
+ <table>
46
+ <caption>{result.title}</caption>
47
+ <thead><tr>{#each result.columns as column (column.id)}<th scope="col">{column.label}</th>{/each}</tr></thead>
48
+ <tbody>{#each result.rows as row, i (i)}<tr>{#each result.columns as column (column.id)}<td>{cell(row[column.id], column)}</td>{/each}</tr>{/each}</tbody>
49
+ </table>
50
+ {:else}
51
+ {#each measures as column (column.id)}
52
+ <Chart title={column.label} style={result.chart.type} {locale} data={{
53
+ points: result.rows.filter(row => typeof row[column.id] === 'number').map(row => ({ label: result.chart.x ? String(row[result.chart.x] ?? '–') : column.label, value: row[column.id] as number })),
54
+ format: column.format === 'money' ? 'money' : column.type === 'integer' ? 'integer' : 'decimal', currency,
55
+ }} />
56
+ {/each}
57
+ {/if}
58
+ {#if result.truncated}<p>{t(M['reports.truncated'])}</p>{/if}
59
+ {/if}
60
+ </div>
61
+
62
+ <style>
63
+ @layer smrt.reset, smrt.structure, smrt.theme;
64
+ @layer smrt.theme {
65
+ [data-smrt-component='ReportResult'] { display: grid; gap: var(--smrt-spacing-3); overflow: auto; }
66
+ table { border-collapse: collapse; inline-size: 100%; }
67
+ th, td { padding: var(--smrt-spacing-2); border-block-end: var(--smrt-hairline); text-align: start; font-variant-numeric: tabular-nums; }
68
+ }
69
+ </style>
@@ -0,0 +1,10 @@
1
+ import type { RuntimeReportResult } from './types.js';
2
+ export interface Props {
3
+ result: RuntimeReportResult;
4
+ locale?: string;
5
+ currency?: string;
6
+ }
7
+ declare const ReportResult: import("svelte").Component<Props, {}, "">;
8
+ type ReportResult = ReturnType<typeof ReportResult>;
9
+ export default ReportResult;
10
+ //# sourceMappingURL=ReportResult.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReportResult.svelte.d.ts","sourceRoot":"","sources":["../../src/svelte/ReportResult.svelte.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAGtD,MAAM,WAAW,KAAK;IACpB,MAAM,EAAE,mBAAmB,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AA6DD,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
@@ -0,0 +1,20 @@
1
+ <script lang="ts">
2
+ import { Chart, type ChartData } from '@happyvertical/smrt-ui/charts';
3
+
4
+ export interface Props {
5
+ options: Record<string, string | number | boolean | null>;
6
+ data?: ChartData;
7
+ title: string;
8
+ locale: string;
9
+ }
10
+ let { options, data, title, locale }: Props = $props();
11
+ </script>
12
+
13
+ <div data-smrt-component="RuntimeReportChartWidget">
14
+ <Chart
15
+ data={data ?? null}
16
+ style={typeof options.style === 'string' ? options.style : 'bar'}
17
+ {title}
18
+ {locale}
19
+ />
20
+ </div>
@@ -0,0 +1,11 @@
1
+ import { type ChartData } from '@happyvertical/smrt-ui/charts';
2
+ export interface Props {
3
+ options: Record<string, string | number | boolean | null>;
4
+ data?: ChartData;
5
+ title: string;
6
+ locale: string;
7
+ }
8
+ declare const RuntimeReportChartWidget: import("svelte").Component<Props, {}, "">;
9
+ type RuntimeReportChartWidget = ReturnType<typeof RuntimeReportChartWidget>;
10
+ export default RuntimeReportChartWidget;
11
+ //# sourceMappingURL=RuntimeReportChartWidget.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RuntimeReportChartWidget.svelte.d.ts","sourceRoot":"","sources":["../../src/svelte/RuntimeReportChartWidget.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAAS,KAAK,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAGtE,MAAM,WAAW,KAAK;IACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC,CAAC;IAC1D,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAaD,QAAA,MAAM,wBAAwB,2CAAwC,CAAC;AACvE,KAAK,wBAAwB,GAAG,UAAU,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAC5E,eAAe,wBAAwB,CAAC"}
@@ -0,0 +1,67 @@
1
+ <script lang="ts">
2
+ import { useI18n } from '@happyvertical/smrt-ui/i18n';
3
+ import { onMount } from 'svelte';
4
+ import { getRuntimeReportsBrowserClient } from './browser-client.js';
5
+ import { M } from './messages.js';
6
+ import ReportDetail from './ReportDetail.svelte';
7
+ import type { RuntimeReportsClient } from './types.js';
8
+
9
+ export interface Props {
10
+ id?: string;
11
+ params?: { id?: string };
12
+ client?: RuntimeReportsClient;
13
+ serverAvailable?: boolean;
14
+ /** Authenticated user id projected by the server route host. */
15
+ currentUserId?: string;
16
+ /** Opaque active tenant/session key used only to isolate browser hints. */
17
+ clientScope?: string;
18
+ /** Server-derived capability hint used by the shared session client. */
19
+ canArchive?: boolean;
20
+ locale?: string;
21
+ currency?: string;
22
+ }
23
+ let {
24
+ id,
25
+ params,
26
+ client,
27
+ serverAvailable = true,
28
+ currentUserId,
29
+ clientScope,
30
+ canArchive = false,
31
+ locale,
32
+ currency,
33
+ }: Props = $props();
34
+ const reportId = $derived(id ?? params?.id ?? '');
35
+ let browserClient = $state<RuntimeReportsClient | undefined>();
36
+ let mounted = $state(false);
37
+ const { t } = useI18n();
38
+ $effect(() => {
39
+ browserClient = client;
40
+ if (!client && mounted && serverAvailable) {
41
+ browserClient = getRuntimeReportsBrowserClient({
42
+ scopeKey: clientScope,
43
+ currentUserId,
44
+ canArchive,
45
+ });
46
+ }
47
+ });
48
+ onMount(() => {
49
+ mounted = true;
50
+ return () => {
51
+ mounted = false;
52
+ };
53
+ });
54
+ </script>
55
+
56
+ <div data-smrt-component="RuntimeReportDetailRoute">
57
+ {#if serverAvailable && !browserClient}
58
+ <p role="status">{t(M['reports.loading'])}</p>
59
+ {:else}
60
+ <ReportDetail
61
+ id={reportId}
62
+ client={serverAvailable ? browserClient : undefined}
63
+ {locale}
64
+ {currency}
65
+ />
66
+ {/if}
67
+ </div>
@@ -0,0 +1,21 @@
1
+ import type { RuntimeReportsClient } from './types.js';
2
+ export interface Props {
3
+ id?: string;
4
+ params?: {
5
+ id?: string;
6
+ };
7
+ client?: RuntimeReportsClient;
8
+ serverAvailable?: boolean;
9
+ /** Authenticated user id projected by the server route host. */
10
+ currentUserId?: string;
11
+ /** Opaque active tenant/session key used only to isolate browser hints. */
12
+ clientScope?: string;
13
+ /** Server-derived capability hint used by the shared session client. */
14
+ canArchive?: boolean;
15
+ locale?: string;
16
+ currency?: string;
17
+ }
18
+ declare const RuntimeReportDetailRoute: import("svelte").Component<Props, {}, "">;
19
+ type RuntimeReportDetailRoute = ReturnType<typeof RuntimeReportDetailRoute>;
20
+ export default RuntimeReportDetailRoute;
21
+ //# sourceMappingURL=RuntimeReportDetailRoute.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RuntimeReportDetailRoute.svelte.d.ts","sourceRoot":"","sources":["../../src/svelte/RuntimeReportDetailRoute.svelte.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAGvD,MAAM,WAAW,KAAK;IACpB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACzB,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,gEAAgE;IAChE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,2EAA2E;IAC3E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wEAAwE;IACxE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAoDD,QAAA,MAAM,wBAAwB,2CAAwC,CAAC;AACvE,KAAK,wBAAwB,GAAG,UAAU,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAC5E,eAAe,wBAAwB,CAAC"}