@happyvertical/smrt-reports 0.57.6 → 0.57.8

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 (74) hide show
  1. package/AGENTS.md +10 -0
  2. package/dist/chunks/runtime-time-D7JZ1e4J.js +1052 -0
  3. package/dist/chunks/runtime-time-D7JZ1e4J.js.map +1 -0
  4. package/dist/index.d.ts +7 -1
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +58 -591
  7. package/dist/index.js.map +1 -1
  8. package/dist/manifest.json +28 -4
  9. package/dist/playground.js +1 -1
  10. package/dist/playground.js.map +1 -1
  11. package/dist/recipes.d.ts +21 -0
  12. package/dist/recipes.d.ts.map +1 -1
  13. package/dist/runtime-compiler.d.ts +7 -40
  14. package/dist/runtime-compiler.d.ts.map +1 -1
  15. package/dist/runtime-dimensions.d.ts +43 -0
  16. package/dist/runtime-dimensions.d.ts.map +1 -0
  17. package/dist/runtime-memory.d.ts +81 -0
  18. package/dist/runtime-memory.d.ts.map +1 -0
  19. package/dist/runtime-plan.d.ts +121 -0
  20. package/dist/runtime-plan.d.ts.map +1 -0
  21. package/dist/runtime-report.d.ts.map +1 -1
  22. package/dist/runtime-server.d.ts.map +1 -1
  23. package/dist/runtime-spec-hash.d.ts +11 -0
  24. package/dist/runtime-spec-hash.d.ts.map +1 -0
  25. package/dist/runtime-spec.d.ts +16 -4
  26. package/dist/runtime-spec.d.ts.map +1 -1
  27. package/dist/runtime-time.d.ts +41 -0
  28. package/dist/runtime-time.d.ts.map +1 -0
  29. package/dist/smrt-knowledge.json +37 -12
  30. package/dist/spec.d.ts +25 -0
  31. package/dist/spec.d.ts.map +1 -0
  32. package/dist/spec.js +2 -0
  33. package/dist/state.js +1 -1
  34. package/dist/state.js.map +1 -1
  35. package/dist/svelte/ReportConfirmation.svelte +97 -24
  36. package/dist/svelte/ReportConfirmation.svelte.d.ts +20 -0
  37. package/dist/svelte/ReportConfirmation.svelte.d.ts.map +1 -1
  38. package/dist/svelte/ReportDetail.svelte +263 -12
  39. package/dist/svelte/ReportDetail.svelte.d.ts +10 -1
  40. package/dist/svelte/ReportDetail.svelte.d.ts.map +1 -1
  41. package/dist/svelte/ReportPicker.svelte +47 -14
  42. package/dist/svelte/ReportPicker.svelte.d.ts +3 -0
  43. package/dist/svelte/ReportPicker.svelte.d.ts.map +1 -1
  44. package/dist/svelte/ReportResult.svelte +216 -35
  45. package/dist/svelte/ReportResult.svelte.d.ts +8 -2
  46. package/dist/svelte/ReportResult.svelte.d.ts.map +1 -1
  47. package/dist/svelte/RuntimeReportWidgetOptions.svelte +9 -1
  48. package/dist/svelte/RuntimeReportWidgetOptions.svelte.d.ts.map +1 -1
  49. package/dist/svelte/SavedReportCard.svelte +242 -0
  50. package/dist/svelte/SavedReportCard.svelte.d.ts +30 -0
  51. package/dist/svelte/SavedReportCard.svelte.d.ts.map +1 -0
  52. package/dist/svelte/SavedReports.svelte +206 -33
  53. package/dist/svelte/SavedReports.svelte.d.ts +13 -0
  54. package/dist/svelte/SavedReports.svelte.d.ts.map +1 -1
  55. package/dist/svelte/SavedReportsWidget.svelte +59 -0
  56. package/dist/svelte/SavedReportsWidget.svelte.d.ts +19 -0
  57. package/dist/svelte/SavedReportsWidget.svelte.d.ts.map +1 -0
  58. package/dist/svelte/client.d.ts.map +1 -1
  59. package/dist/svelte/client.js +3 -0
  60. package/dist/svelte/describe.d.ts +94 -0
  61. package/dist/svelte/describe.d.ts.map +1 -0
  62. package/dist/svelte/describe.js +249 -0
  63. package/dist/svelte/index.d.ts +4 -1
  64. package/dist/svelte/index.d.ts.map +1 -1
  65. package/dist/svelte/index.js +4 -1
  66. package/dist/svelte/messages.d.ts +48 -0
  67. package/dist/svelte/messages.d.ts.map +1 -1
  68. package/dist/svelte/messages.js +59 -11
  69. package/dist/svelte/playground.d.ts +2 -2
  70. package/dist/svelte/playground.d.ts.map +1 -1
  71. package/dist/svelte/playground.js +1 -1
  72. package/dist/svelte/types.d.ts +2 -0
  73. package/dist/svelte/types.d.ts.map +1 -1
  74. package/package.json +13 -9
@@ -1,39 +1,104 @@
1
1
  <script lang="ts">
2
- import { Checkbox } from '@happyvertical/smrt-ui/forms';
2
+ /**
3
+ * Saved reports as a grid of cards, each run for its headline figure. `page`
4
+ * is the Saved reports page (its heading, "New report", whose and archived
5
+ * filters); `section` is the Reports section page's widget (a hint, "New
6
+ * report", "View all"). "New report" asks the page's assistant (smrt-ui
7
+ * `requestAssistant`); with none on the page it says where to ask.
8
+ */
9
+ import { requestAssistant } from '@happyvertical/smrt-ui/chat';
10
+ import { ConfirmDialog } from '@happyvertical/smrt-ui/feedback';
11
+ import { SegmentedControl, Switch } from '@happyvertical/smrt-ui/forms';
3
12
  import { useI18n } from '@happyvertical/smrt-ui/i18n';
4
- import { Button } from '@happyvertical/smrt-ui/ui';
13
+ import { EmptyState } from '@happyvertical/smrt-ui/layout';
14
+ import { Button, Skeleton } from '@happyvertical/smrt-ui/ui';
5
15
  import { untrack } from 'svelte';
16
+ import type { ReportSourceInfo } from './describe.js';
6
17
  import { M } from './messages.js';
7
- import { reportError } from './result.js';
8
- import type { RuntimeReportsClient, SavedRuntimeReport } from './types.js';
18
+ import { reportError, validReportResult } from './result.js';
19
+ import SavedReportCard from './SavedReportCard.svelte';
20
+ import type {
21
+ RuntimeReportResult,
22
+ RuntimeReportsClient,
23
+ SavedRuntimeReport,
24
+ } from './types.js';
9
25
 
10
26
  export interface Props {
11
27
  client?: RuntimeReportsClient;
12
28
  reportHref?: (id: string) => string;
13
29
  /** Host-owned identity projection; report transport never expands users. */
14
30
  authorLabel?: (userId: string) => string | undefined;
31
+ /** The report sources by label and fields (`reports.runtime.sources`). */
32
+ sources?: readonly ReportSourceInfo[];
33
+ /** `page` (default) or `section`, the Reports section page's widget. */
34
+ variant?: 'page' | 'section';
35
+ /** Most cards shown (and run); the section shows its first few. */
36
+ limit?: number;
37
+ /** The Saved reports page, for the section's "View all". */
38
+ listHref?: string;
39
+ /** Replace "New report" asking the page's assistant. */
40
+ onnewreport?: () => void;
41
+ locale?: string;
42
+ currency?: string;
15
43
  }
16
44
  let {
17
45
  client,
18
46
  reportHref = (id) => `/reports/${encodeURIComponent(id)}`,
19
- authorLabel = (id) => id,
47
+ authorLabel = () => undefined,
48
+ sources = [],
49
+ variant = 'page',
50
+ limit = variant === 'section' ? 6 : 48,
51
+ listHref,
52
+ onnewreport,
53
+ locale = 'en',
54
+ currency = 'USD',
20
55
  }: Props = $props();
21
56
  const { t } = useI18n();
22
- let onlyMine = $state(false);
57
+ let whose = $state<'all' | 'mine'>('all');
23
58
  let includeArchived = $state(false);
24
59
  let rows = $state<SavedRuntimeReport[]>([]);
60
+ let total = $state(0);
61
+ let results = $state<Record<string, RuntimeReportResult | null>>({});
25
62
  let loading = $state(false);
26
63
  let error = $state<ReturnType<typeof reportError> | null>(null);
27
64
  let refresh = $state(0);
28
65
  let pending = $state<string | null>(null);
66
+ let confirming = $state<SavedRuntimeReport | null>(null);
67
+ let askNote = $state(false);
29
68
  let epoch = 0;
30
69
 
70
+ /** Run the shown reports a few at a time, for their cards' figures. */
71
+ async function runAll(
72
+ transport: RuntimeReportsClient,
73
+ reports: SavedRuntimeReport[],
74
+ current: number,
75
+ ) {
76
+ const queue = reports.filter((r) => r.status === 'active');
77
+ const worker = async () => {
78
+ for (let next = queue.shift(); next; next = queue.shift()) {
79
+ const id = next.id;
80
+ let value: RuntimeReportResult | null = null;
81
+ try {
82
+ const ran = await transport.run(id);
83
+ value = validReportResult(ran) ? ran : null;
84
+ } catch {
85
+ value = null;
86
+ }
87
+ if (current !== epoch) return;
88
+ results = { ...results, [id]: value };
89
+ }
90
+ };
91
+ await Promise.all([worker(), worker(), worker()]);
92
+ }
93
+
31
94
  $effect(() => {
32
95
  const transport = client;
33
- const options = { onlyMine, includeArchived };
96
+ const options = { onlyMine: whose === 'mine', includeArchived };
97
+ const max = limit;
34
98
  refresh;
35
99
  const current = ++epoch;
36
100
  rows = [];
101
+ results = {};
37
102
  error = null;
38
103
  pending = null;
39
104
  loading = !!transport;
@@ -41,7 +106,10 @@ $effect(() => {
41
106
  void transport
42
107
  .list(options)
43
108
  .then((value) => {
44
- if (current === epoch) rows = value;
109
+ if (current !== epoch) return;
110
+ total = value.length;
111
+ rows = value.slice(0, max);
112
+ void runAll(transport, rows, current);
45
113
  })
46
114
  .catch((e) => {
47
115
  if (current === epoch) error = reportError(e);
@@ -55,60 +123,165 @@ $effect(() => {
55
123
  };
56
124
  });
57
125
 
58
- async function archive(report: SavedRuntimeReport) {
126
+ async function setArchived(report: SavedRuntimeReport, archived: boolean) {
59
127
  if (!client || pending || !report.canArchive) return;
60
128
  const current = epoch;
61
129
  pending = report.id;
62
130
  error = null;
63
131
  try {
64
- await client.archive(report.id, report.status !== 'archived');
132
+ await client.archive(report.id, archived);
65
133
  if (current === epoch) refresh = untrack(() => refresh) + 1;
66
134
  } catch (e) {
67
135
  if (current === epoch) error = reportError(e);
68
136
  } finally {
69
137
  if (current === epoch) pending = null;
138
+ confirming = null;
70
139
  }
71
140
  }
141
+
142
+ function actionsFor(report: SavedRuntimeReport) {
143
+ if (!report.canArchive) return [];
144
+ return report.status === 'archived'
145
+ ? [
146
+ {
147
+ id: 'restore',
148
+ label: t(M['reports.restore']),
149
+ disabled: pending !== null,
150
+ onselect: () => void setArchived(report, false),
151
+ },
152
+ ]
153
+ : [
154
+ {
155
+ id: 'archive',
156
+ label: t(M['reports.archive']),
157
+ disabled: pending !== null,
158
+ onselect: () => {
159
+ confirming = report;
160
+ },
161
+ },
162
+ ];
163
+ }
164
+
165
+ let root = $state<HTMLElement | null>(null);
166
+ function newReport() {
167
+ if (onnewreport) {
168
+ onnewreport();
169
+ return;
170
+ }
171
+ askNote = !(
172
+ root && requestAssistant(root, { prompt: t(M['reports.new_prompt']) })
173
+ );
174
+ }
175
+ const sourceOf = (id: string) => sources.find((s) => s.id === id);
176
+ const headingLevel = $derived(variant === 'page' ? 2 : 3);
72
177
  </script>
73
178
 
74
- <section data-smrt-component="SavedReports" aria-label={t(M['reports.title'])}>
75
- <h1>{t(M['reports.title'])}</h1>
179
+ <section
180
+ bind:this={root}
181
+ class="saved-reports"
182
+ data-smrt-component="SavedReports"
183
+ data-variant={variant}
184
+ aria-label={t(M['reports.title'])}
185
+ >
186
+ {#if variant === 'page'}
187
+ <header class="saved-reports__head">
188
+ <h1 class="saved-reports__title">{t(M['reports.title'])}</h1>
189
+ {#if client}<Button variant="primary" onclick={newReport}>{t(M['reports.new'])}</Button>{/if}
190
+ </header>
191
+ {/if}
76
192
  {#if !client}
77
- <p>{t(M['reports.unavailable'])}</p>
193
+ <p class="saved-reports__note">{t(M['reports.unavailable'])}</p>
78
194
  {:else}
79
- <div data-smrt-part="filters">
80
- <Checkbox bind:checked={onlyMine} label={t(M['reports.only_mine'])} />
81
- <Checkbox bind:checked={includeArchived} label={t(M['reports.archived'])} />
82
- </div>
83
- {#if error}<p role="alert">{t(M[error])}</p><Button onclick={() => refresh++}>{t(M['reports.retry'])}</Button>{/if}
195
+ {#if variant === 'page'}
196
+ <div class="saved-reports__toolbar">
197
+ <SegmentedControl
198
+ label={t(M['reports.filter_label'])}
199
+ options={[
200
+ { value: 'all', label: t(M['reports.filter_all']) },
201
+ { value: 'mine', label: t(M['reports.filter_mine']) },
202
+ ]}
203
+ value={whose}
204
+ onvaluechange={(value) => (whose = value === 'mine' ? 'mine' : 'all')}
205
+ />
206
+ <Switch bind:checked={includeArchived} label={t(M['reports.archived'])} size="sm" />
207
+ </div>
208
+ {:else if rows.length > 0}
209
+ <div class="saved-reports__toolbar">
210
+ <p class="saved-reports__hint">{t(M['reports.hint'])}</p>
211
+ <div class="saved-reports__actions">
212
+ {#if listHref && total > rows.length}<Button variant="ghost" size="sm" href={listHref}>{t(M['reports.view_all'])}</Button>{/if}
213
+ <Button variant="primary" size="sm" onclick={newReport}>{t(M['reports.new'])}</Button>
214
+ </div>
215
+ </div>
216
+ {/if}
217
+ {#if askNote}<p class="saved-reports__note" role="status">{t(M['reports.ask_where'])}</p>{/if}
218
+ {#if error}
219
+ <div class="saved-reports__error">
220
+ <p role="alert">{t(M[error])}</p>
221
+ <Button variant="secondary" size="sm" onclick={() => refresh++}>{t(M['reports.retry'])}</Button>
222
+ </div>
223
+ {/if}
84
224
  {#if loading}
85
- <p role="status">{t(M['reports.loading'])}</p>
225
+ <ul class="saved-reports__grid" aria-busy="true" aria-label={t(M['reports.loading'])}>
226
+ {#each [0, 1, 2] as index (index)}
227
+ <li class="saved-reports__placeholder"><Skeleton variant="text" lines={4} /></li>
228
+ {/each}
229
+ </ul>
86
230
  {:else if rows.length === 0 && !error}
87
- <p>{t(M['reports.empty'])}</p>
88
- {:else}
89
- <ul>
231
+ <EmptyState
232
+ title={t(M['reports.empty_title'])}
233
+ description={t(M['reports.empty'])}
234
+ icon="document"
235
+ actionLabel={t(M['reports.new'])}
236
+ onaction={newReport}
237
+ size={variant === 'page' ? 'md' : 'sm'}
238
+ />
239
+ {:else if rows.length > 0}
240
+ <ul class="saved-reports__grid">
90
241
  {#each rows as report (report.id)}
91
242
  <li>
92
- <h2><a href={reportHref(report.id)}>{report.title}</a></h2>
93
- <dl>
94
- <dt>{t(M['reports.source'])}</dt><dd>{report.sourceId}</dd>
95
- <dt>{t(M['reports.author'])}</dt><dd>{authorLabel(report.createdByUserId) ?? report.createdByUserId}</dd>
96
- </dl>
97
- <p>{report.lastRunAt ? t(M['reports.last_run'], { time: report.lastRunAt }) : t(M['reports.never_run'])}</p>
98
- {#if report.canArchive}<Button disabled={pending !== null} onclick={() => archive(report)}>{t(M[report.status === 'archived' ? 'reports.unarchive' : 'reports.archive'])}</Button>{/if}
243
+ <SavedReportCard
244
+ {report}
245
+ href={reportHref(report.id)}
246
+ result={report.status === 'archived' ? null : results[report.id]}
247
+ source={sourceOf(report.sourceId)}
248
+ actions={actionsFor(report)}
249
+ author={authorLabel(report.createdByUserId)}
250
+ {headingLevel}
251
+ {locale}
252
+ {currency}
253
+ />
99
254
  </li>
100
255
  {/each}
101
256
  </ul>
102
257
  {/if}
103
258
  {/if}
259
+ <ConfirmDialog
260
+ open={confirming !== null}
261
+ title={t(M['reports.archive_confirm_title'], { title: confirming?.title ?? '' })}
262
+ message={t(M['reports.archive_confirm'])}
263
+ confirmLabel={t(M['reports.archive'])}
264
+ cancelLabel={t(M['reports.cancel'])}
265
+ loading={pending !== null}
266
+ onconfirm={() => confirming && void setArchived(confirming, true)}
267
+ oncancel={() => (confirming = null)}
268
+ />
104
269
  </section>
105
270
 
106
271
  <style>
107
272
  @layer smrt.reset, smrt.structure, smrt.theme;
108
273
  @layer smrt.theme {
109
- [data-smrt-part='filters'] { display: flex; flex-wrap: wrap; gap: var(--smrt-spacing-3); }
110
- ul { display: grid; gap: var(--smrt-spacing-4); padding: 0; list-style: none; }
111
- li { padding: var(--smrt-spacing-4); border: var(--smrt-hairline); border-radius: var(--smrt-radius-medium); }
112
- dl { display: grid; grid-template-columns: auto 1fr; gap: var(--smrt-spacing-2); }
274
+ .saved-reports { display: grid; gap: var(--smrt-spacing-4); min-inline-size: 0; container-type: inline-size; }
275
+ .saved-reports[data-variant='section'] { gap: var(--smrt-spacing-3); }
276
+ .saved-reports__head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--smrt-spacing-3); }
277
+ .saved-reports__title { margin: 0; font: var(--smrt-typography-headline-medium-font); }
278
+ .saved-reports__toolbar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--smrt-spacing-2) var(--smrt-spacing-4); }
279
+ .saved-reports__actions { display: flex; flex-wrap: wrap; gap: var(--smrt-spacing-2); }
280
+ .saved-reports__hint, .saved-reports__note { margin: 0; color: var(--smrt-color-on-surface-variant); font-size: var(--smrt-typography-body-medium-size); }
281
+ .saved-reports__error { display: flex; flex-wrap: wrap; align-items: center; gap: var(--smrt-spacing-3); color: var(--smrt-color-error); }
282
+ .saved-reports__error p { margin: 0; }
283
+ .saved-reports__grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 17rem), 1fr)); gap: var(--smrt-spacing-3); margin: 0; padding: 0; list-style: none; }
284
+ .saved-reports__grid > li { display: grid; min-inline-size: 0; }
285
+ .saved-reports__placeholder { padding: var(--smrt-spacing-4); border: var(--smrt-hairline); border-radius: var(--smrt-radius-large); }
113
286
  }
114
287
  </style>
@@ -1,9 +1,22 @@
1
+ import type { ReportSourceInfo } from './describe.js';
1
2
  import type { RuntimeReportsClient } from './types.js';
2
3
  export interface Props {
3
4
  client?: RuntimeReportsClient;
4
5
  reportHref?: (id: string) => string;
5
6
  /** Host-owned identity projection; report transport never expands users. */
6
7
  authorLabel?: (userId: string) => string | undefined;
8
+ /** The report sources by label and fields (`reports.runtime.sources`). */
9
+ sources?: readonly ReportSourceInfo[];
10
+ /** `page` (default) or `section`, the Reports section page's widget. */
11
+ variant?: 'page' | 'section';
12
+ /** Most cards shown (and run); the section shows its first few. */
13
+ limit?: number;
14
+ /** The Saved reports page, for the section's "View all". */
15
+ listHref?: string;
16
+ /** Replace "New report" asking the page's assistant. */
17
+ onnewreport?: () => void;
18
+ locale?: string;
19
+ currency?: string;
7
20
  }
8
21
  declare const SavedReports: import("svelte").Component<Props, {}, "">;
9
22
  type SavedReports = ReturnType<typeof SavedReports>;
@@ -1 +1 @@
1
- {"version":3,"file":"SavedReports.svelte.d.ts","sourceRoot":"","sources":["../../src/svelte/SavedReports.svelte.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,oBAAoB,EAAsB,MAAM,YAAY,CAAC;AAG3E,MAAM,WAAW,KAAK;IACpB,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B,UAAU,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,4EAA4E;IAC5E,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;CACtD;AAwGD,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"SavedReports.svelte.d.ts","sourceRoot":"","sources":["../../src/svelte/SavedReports.svelte.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAItD,OAAO,KAAK,EAEV,oBAAoB,EAErB,MAAM,YAAY,CAAC;AAGpB,MAAM,WAAW,KAAK;IACpB,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B,UAAU,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,4EAA4E;IAC5E,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACrD,0EAA0E;IAC1E,OAAO,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACtC,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,mEAAmE;IACnE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAkND,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
@@ -0,0 +1,59 @@
1
+ <script lang="ts">
2
+ /**
3
+ * The `saved-reports` overview widget: the Reports section page's default
4
+ * (`sectionDefault`), the team's saved reports as cards with their headline
5
+ * figures, "New report" (asks the page's assistant) and a hint. It reads
6
+ * through the shared browser client of the app's runtime-report routes; a
7
+ * host with its own transport (the planner's sample data) passes `client`.
8
+ */
9
+ import { onMount } from 'svelte';
10
+ import { getRuntimeReportsBrowserClient } from './browser-client.js';
11
+ import type { ReportSourceInfo } from './describe.js';
12
+ import SavedReports from './SavedReports.svelte';
13
+ import type { RuntimeReportsClient } from './types.js';
14
+
15
+ export interface Props {
16
+ options?: Record<string, string | number | boolean | null>;
17
+ title?: string;
18
+ locale?: string;
19
+ /** A host-owned transport; omitted, the app's shared browser client. */
20
+ client?: RuntimeReportsClient;
21
+ reportHref?: (id: string) => string;
22
+ /** The Saved reports page ("View all"); default `/reports`. */
23
+ listHref?: string;
24
+ sources?: readonly ReportSourceInfo[];
25
+ authorLabel?: (userId: string) => string | undefined;
26
+ currency?: string;
27
+ }
28
+ let {
29
+ options = {},
30
+ locale = 'en',
31
+ client,
32
+ reportHref,
33
+ listHref = '/reports',
34
+ sources,
35
+ authorLabel,
36
+ currency,
37
+ }: Props = $props();
38
+ let shared = $state<RuntimeReportsClient | undefined>();
39
+ onMount(() => {
40
+ if (!client) shared = getRuntimeReportsBrowserClient();
41
+ });
42
+ const limit = $derived(
43
+ typeof options.limit === 'number' && options.limit > 0 ? options.limit : 6,
44
+ );
45
+ </script>
46
+
47
+ <div data-smrt-component="SavedReportsWidget">
48
+ <SavedReports
49
+ variant="section"
50
+ client={client ?? shared}
51
+ {limit}
52
+ {reportHref}
53
+ {listHref}
54
+ {sources}
55
+ {authorLabel}
56
+ {locale}
57
+ {currency}
58
+ />
59
+ </div>
@@ -0,0 +1,19 @@
1
+ import type { ReportSourceInfo } from './describe.js';
2
+ import type { RuntimeReportsClient } from './types.js';
3
+ export interface Props {
4
+ options?: Record<string, string | number | boolean | null>;
5
+ title?: string;
6
+ locale?: string;
7
+ /** A host-owned transport; omitted, the app's shared browser client. */
8
+ client?: RuntimeReportsClient;
9
+ reportHref?: (id: string) => string;
10
+ /** The Saved reports page ("View all"); default `/reports`. */
11
+ listHref?: string;
12
+ sources?: readonly ReportSourceInfo[];
13
+ authorLabel?: (userId: string) => string | undefined;
14
+ currency?: string;
15
+ }
16
+ declare const SavedReportsWidget: import("svelte").Component<Props, {}, "">;
17
+ type SavedReportsWidget = ReturnType<typeof SavedReportsWidget>;
18
+ export default SavedReportsWidget;
19
+ //# sourceMappingURL=SavedReportsWidget.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SavedReportsWidget.svelte.d.ts","sourceRoot":"","sources":["../../src/svelte/SavedReportsWidget.svelte.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAGvD,MAAM,WAAW,KAAK;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC,CAAC;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B,UAAU,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACtC,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAkCD,QAAA,MAAM,kBAAkB,2CAAwC,CAAC;AACjE,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAChE,eAAe,kBAAkB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/svelte/client.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEV,oBAAoB,EACpB,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,+BAA+B;IAC9C,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iEAAiE;IACjE,UAAU,CAAC,EACP,OAAO,GACP,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,kBAAkB,EAAE,YAAY,CAAC,KAAK,OAAO,CAAC,CAAC;CACnE;AAED,qBAAa,uBAAwB,SAAQ,KAAK;IAE9C,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM;gBADb,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,MAAM,YAAA;CAKzB;AA0CD,wBAAgB,8BAA8B,CAC5C,OAAO,GAAE,+BAAoC,GAC5C,oBAAoB,CAoKtB"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/svelte/client.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEV,oBAAoB,EACpB,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,+BAA+B;IAC9C,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iEAAiE;IACjE,UAAU,CAAC,EACP,OAAO,GACP,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,kBAAkB,EAAE,YAAY,CAAC,KAAK,OAAO,CAAC,CAAC;CACnE;AAED,qBAAa,uBAAwB,SAAQ,KAAK;IAE9C,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM;gBADb,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,MAAM,YAAA;CAKzB;AA0CD,wBAAgB,8BAA8B,CAC5C,OAAO,GAAE,+BAAoC,GAC5C,oBAAoB,CAuKtB"}
@@ -73,6 +73,9 @@ export function createRuntimeReportsHttpClient(options = {}) {
73
73
  createdByUserId: row.createdByUserId,
74
74
  status: row.status,
75
75
  spec,
76
+ ...(typeof row.createdAt === 'string' && row.createdAt
77
+ ? { savedAt: row.createdAt }
78
+ : {}),
76
79
  ...(lastRuns.has(row.id) ? { lastRunAt: lastRuns.get(row.id) } : {}),
77
80
  };
78
81
  const canArchive = typeof options.canArchive === 'function'
@@ -0,0 +1,94 @@
1
+ import type { RuntimeReportResult, RuntimeReportSpec } from './types.js';
2
+ /** A source field as a report screen names and formats it (its descriptor's `fields`). */
3
+ export interface ReportFieldInfo {
4
+ name: string;
5
+ label: string;
6
+ /** `datetime`, `integer`, `decimal`, `text`... */
7
+ type?: string;
8
+ /** `money`, `date`... */
9
+ format?: string;
10
+ }
11
+ /** A source by label, with its fields (a `reports.runtime.sources` descriptor). */
12
+ export interface ReportSourceInfo {
13
+ id: string;
14
+ label: string;
15
+ fields?: readonly ReportFieldInfo[];
16
+ }
17
+ type Translate = (key: string, values?: Record<string, unknown>) => string;
18
+ type Column = RuntimeReportResult['columns'][number];
19
+ export declare function humanizeName(name: string): string;
20
+ export interface ReportPeriod {
21
+ /** The field the period is on ("Issue date"). */
22
+ field: string;
23
+ /** The dates it covers ("1 Dec 2025 – 31 Dec 2025"), in UTC. */
24
+ range: string;
25
+ /** The indexes of the filters it consumed. */
26
+ filters: number[];
27
+ }
28
+ /**
29
+ * The spec's period: a lower (`gte`/`gt`) and/or upper (`lt`/`lte`) bound on
30
+ * one date field. An exclusive upper bound at midnight reads as the day
31
+ * before, so "last month" reads 1–31 December.
32
+ */
33
+ export declare function reportPeriod(spec: RuntimeReportSpec, options: {
34
+ locale: string;
35
+ label: (field: string) => string;
36
+ t: Translate;
37
+ }): ReportPeriod | null;
38
+ export interface ReportDescription {
39
+ source: string;
40
+ /** "Issue date by month", or "" for an ungrouped report. */
41
+ groupings: string;
42
+ period: ReportPeriod | null;
43
+ measures: string[];
44
+ /** Conditions other than the period. */
45
+ filters: string[];
46
+ having: string[];
47
+ /** One line for a card: source · groupings · period. */
48
+ line: string;
49
+ }
50
+ /** Describe a spec in the source's own words. */
51
+ export declare function describeReport(spec: RuntimeReportSpec, options: {
52
+ t: Translate;
53
+ locale: string;
54
+ currency: string;
55
+ source?: ReportSourceInfo;
56
+ /** A result's columns, for measure labels ("Total amount"). */
57
+ columns?: readonly Column[];
58
+ }): ReportDescription;
59
+ /** Does the result's x axis run over time buckets? */
60
+ export declare function timeAxis(result: RuntimeReportResult): Column | undefined;
61
+ /** The measure columns a result charts, in order. */
62
+ /** The measure columns, the charted ones (`chart.y`) first. */
63
+ export declare function measureColumns(result: RuntimeReportResult): Column[];
64
+ /** A measure's chart format. */
65
+ export declare function measureFormat(column: Column): 'money' | 'integer' | 'decimal';
66
+ export interface ReportFigure {
67
+ /** What the figure is ("Total", "Average per month", "Peak"). */
68
+ label: string;
69
+ value: string;
70
+ /** Where the peak was ("Sep 2025"). */
71
+ detail?: string;
72
+ }
73
+ /**
74
+ * Headline figures for one measure: the total (only for an additive measure:
75
+ * a sum or a row count), the average per group, and the peak with its group.
76
+ * An ungrouped report has one value, its own figure.
77
+ */
78
+ export declare function reportFigures(result: RuntimeReportResult, measure: Column, options: {
79
+ t: Translate;
80
+ locale: string;
81
+ currency: string;
82
+ /** How a group reads ("Sep 2025" for a time bucket). */
83
+ groupLabel: (row: RuntimeReportResult['rows'][number]) => string;
84
+ /** The measure's function, when the spec is known. */
85
+ fn?: string;
86
+ }): ReportFigure[];
87
+ /** "3 days ago", "in 2 hours", "just now": a time relative to `now`. */
88
+ export declare function relativeTime(iso: string, locale: string, now?: Date,
89
+ /** What under a minute reads as ("just now"); default the locale's "now". */
90
+ justNow?: string): string;
91
+ /** The result as CSV with the screen's own formatting (labels, buckets, money). */
92
+ export declare function reportCsv(result: RuntimeReportResult, cell: (value: unknown, column: Column) => string): string;
93
+ export {};
94
+ //# sourceMappingURL=describe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"describe.d.ts","sourceRoot":"","sources":["../../src/svelte/describe.ts"],"names":[],"mappings":"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"}