@lobb-js/lobb-ext-reports 0.3.1 → 0.3.3

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 (41) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.js +20 -0
  3. package/dist/lib/components/dv_fields_buttons/query_ai_button/index.svelte +27 -0
  4. package/dist/lib/components/dv_fields_buttons/query_ai_button/index.svelte.d.ts +14 -0
  5. package/dist/lib/components/pages/reports/components/chart.svelte +131 -0
  6. package/dist/lib/components/pages/reports/components/chart.svelte.d.ts +14 -0
  7. package/dist/lib/components/pages/reports/components/charts/chartJs.svelte +42 -0
  8. package/dist/lib/components/pages/reports/components/charts/chartJs.svelte.d.ts +14 -0
  9. package/dist/lib/components/pages/reports/components/charts/table.svelte +36 -0
  10. package/dist/lib/components/pages/reports/components/charts/table.svelte.d.ts +14 -0
  11. package/dist/lib/components/pages/reports/components/report.svelte +156 -0
  12. package/dist/lib/components/pages/reports/components/report.svelte.d.ts +14 -0
  13. package/dist/lib/components/pages/reports/index.svelte +141 -0
  14. package/dist/lib/components/pages/reports/index.svelte.d.ts +14 -0
  15. package/dist/lib/index.d.ts +0 -0
  16. package/dist/lib/index.js +2 -0
  17. package/dist/lib/utils.d.ts +12 -0
  18. package/dist/lib/utils.js +5 -0
  19. package/dist/tests/analytics.spec.d.ts +1 -0
  20. package/dist/tests/analytics.spec.js +17 -0
  21. package/dist/tests/package.json +1 -0
  22. package/dist/tests/playwright.config.cjs +27 -0
  23. package/dist/tests/playwright.config.d.cts +2 -0
  24. package/package.json +7 -3
  25. package/.dockerignore +0 -7
  26. package/.env.example +0 -10
  27. package/.vscode/settings.json +0 -5
  28. package/CHANGELOG.md +0 -132
  29. package/lobb.ts +0 -55
  30. package/scripts/postpublish.sh +0 -12
  31. package/scripts/prepublish.sh +0 -17
  32. package/studio/app.html +0 -12
  33. package/studio/routes/+layout.svelte +0 -7
  34. package/studio/routes/+layout.ts +0 -1
  35. package/studio/routes/[...path]/+page.svelte +0 -6
  36. package/svelte.config.js +0 -24
  37. package/todo.md +0 -11
  38. package/tsconfig.app.json +0 -27
  39. package/tsconfig.json +0 -13
  40. package/tsconfig.node.json +0 -26
  41. package/vite.config.ts +0 -8
@@ -0,0 +1,2 @@
1
+ import type { Extension, ExtensionUtils } from "@lobb-js/studio";
2
+ export default function extension(utils: ExtensionUtils): Extension;
package/dist/index.js ADDED
@@ -0,0 +1,20 @@
1
+ import Reports from "./lib/components/pages/reports/index.svelte";
2
+ import QueryAiButton from "./lib/components/dv_fields_buttons/query_ai_button/index.svelte";
3
+ export default function extension(utils) {
4
+ return {
5
+ name: "reports",
6
+ components: {
7
+ "dvFields.topRight.reports_charts.query": QueryAiButton,
8
+ "pages.analytics": Reports,
9
+ },
10
+ dashboardNavs: {
11
+ middle: [
12
+ {
13
+ href: "/studio/extensions/reports/analytics",
14
+ icon: utils.components.Icons.ChartNoAxesCombined,
15
+ label: "Analytics",
16
+ },
17
+ ],
18
+ },
19
+ };
20
+ }
@@ -0,0 +1,27 @@
1
+ <script lang="ts">
2
+ import type { ExtensionProps } from "@lobb-js/studio";
3
+
4
+ let { utils, value = $bindable() }: ExtensionProps = $props();
5
+
6
+ let systemMessage = `You are an expert assistant specialized in generating PostgreSQL read-only queries for analytics and reporting purposes. Your job is to translate user requests written in natural language into accurate, efficient, and well-structured SQL SELECT queries. Only respond with a raw PostgreSQL SELECT query — do not include any markdown formatting, code blocks, or explanations.. This is the schema of the database: (${JSON.stringify(utils.ctx.meta.collections)})`;
7
+
8
+ const componentProps: any = {
9
+ title: "Generate the query with AI",
10
+ description: "Describe to the AI the query you want to generate",
11
+ class: "h-7 px-3 text-xs font-normal",
12
+ variant: "outline",
13
+ // format: {
14
+ // type: 'json_object'
15
+ // },
16
+ messages: [
17
+ {
18
+ role: "system",
19
+ content: systemMessage,
20
+ },
21
+ ],
22
+ };
23
+ </script>
24
+
25
+ <utils.components.LlmButton bind:value {...componentProps}>
26
+ Generate with AI
27
+ </utils.components.LlmButton>
@@ -0,0 +1,14 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+ declare const __propDef: {
3
+ props: Record<string, never>;
4
+ events: {
5
+ [evt: string]: CustomEvent<any>;
6
+ };
7
+ slots: {};
8
+ };
9
+ export type IndexProps = typeof __propDef.props;
10
+ export type IndexEvents = typeof __propDef.events;
11
+ export type IndexSlots = typeof __propDef.slots;
12
+ export default class Index extends SvelteComponentTyped<IndexProps, IndexEvents, IndexSlots> {
13
+ }
14
+ export {};
@@ -0,0 +1,131 @@
1
+ <script lang="ts">
2
+ import type { ExtensionProps } from "@lobb-js/studio";
3
+ import Table from "./charts/table.svelte";
4
+ import ChartJs from "./charts/chartJs.svelte";
5
+
6
+ interface Props extends ExtensionProps {
7
+ chartRecord: any;
8
+ onChartDeleted?: () => Promise<void>;
9
+ onChartEdited?: () => Promise<void>;
10
+ context?: Record<string, any>;
11
+ }
12
+
13
+ const {
14
+ chartRecord,
15
+ onChartDeleted,
16
+ onChartEdited,
17
+ context,
18
+ ...props
19
+ }: Props = $props();
20
+
21
+ const utils = props.utils;
22
+ const { UpdateDetailViewButton, Button } = utils.components;
23
+ const icons = utils.components.Icons;
24
+ let dataPromise = $state(getChartData());
25
+
26
+ async function getChartData() {
27
+ const response = await utils.lobb.findOne(
28
+ "reports_charts",
29
+ chartRecord.id,
30
+ {
31
+ action: "run_query",
32
+ context: JSON.stringify(context),
33
+ },
34
+ );
35
+
36
+ const result = await response.json();
37
+ return result;
38
+ }
39
+
40
+ async function handleChartDelete() {
41
+ const result = await utils.showDialog(
42
+ "Are you sure?",
43
+ "This will delete that chart you selected.",
44
+ );
45
+ if (result) {
46
+ await utils.lobb.deleteOne("reports_charts", chartRecord.id);
47
+ if (onChartDeleted) {
48
+ await onChartDeleted();
49
+ }
50
+ }
51
+ }
52
+
53
+ async function handleEditOnSuccessfull() {
54
+ if (onChartEdited) {
55
+ await onChartEdited();
56
+ }
57
+ }
58
+ </script>
59
+
60
+ <div class="gridContainer h-96 rounded-md border bg-background">
61
+ <div class="flex items-center justify-between border-b p-2 text-sm">
62
+ <div>{chartRecord.title}</div>
63
+ <div class="flex">
64
+ <UpdateDetailViewButton
65
+ collectionName="reports_charts"
66
+ recordId={chartRecord.id}
67
+ variant="ghost"
68
+ class="h-7 w-7 text-muted-foreground hover:bg-transparent"
69
+ Icon={icons.Pencil}
70
+ onSuccessfullSave={handleEditOnSuccessfull}
71
+ ></UpdateDetailViewButton>
72
+ <Button
73
+ class="h-6 w-6 text-muted-foreground hover:bg-transparent"
74
+ variant="ghost"
75
+ size="icon"
76
+ Icon={icons.Trash}
77
+ onclick={handleChartDelete}
78
+ ></Button>
79
+ </div>
80
+ </div>
81
+ <div class="flex items-center justify-center overflow-auto">
82
+ <svelte:boundary>
83
+ {#snippet failed(error, reset)}
84
+ <div
85
+ class="flex flex-col gap-2 p-2 bg-red-100 border border-red-500 h-full"
86
+ >
87
+ <div>{error}</div>
88
+ <button
89
+ class="border border-red-500 w-fit px-4 rounded-md hover:bg-red-500"
90
+ onclick={reset}>Reset</button
91
+ >
92
+ </div>
93
+ {/snippet}
94
+ {#await dataPromise}
95
+ <div
96
+ class="flex flex-col gap-4 h-full w-full absolute justify-center items-center"
97
+ >
98
+ <icons.LoaderCircle
99
+ class="animate-spin opacity-50"
100
+ size="50"
101
+ />
102
+ <div class="flex flex-col items-center justify-center">
103
+ <div class="text-muted-foreground">
104
+ Loading the chart...
105
+ </div>
106
+ <div class="text-muted-foreground text-xs">
107
+ Loading chart's data
108
+ </div>
109
+ </div>
110
+ </div>
111
+ {:then data}
112
+ {#if data.type === "table"}
113
+ <Table input={data.input} {...props} />
114
+ {:else if data.type === "chartjs"}
115
+ <ChartJs input={data.input} {...props} />
116
+ {:else}
117
+ <div class="text-muted-foreground">
118
+ The ({data.type}) chart type is unsupported
119
+ </div>
120
+ {/if}
121
+ {/await}
122
+ </svelte:boundary>
123
+ </div>
124
+ </div>
125
+
126
+ <style>
127
+ .gridContainer {
128
+ display: grid;
129
+ grid-template-rows: auto 1fr;
130
+ }
131
+ </style>
@@ -0,0 +1,14 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+ declare const __propDef: {
3
+ props: Record<string, never>;
4
+ events: {
5
+ [evt: string]: CustomEvent<any>;
6
+ };
7
+ slots: {};
8
+ };
9
+ export type ChartProps = typeof __propDef.props;
10
+ export type ChartEvents = typeof __propDef.events;
11
+ export type ChartSlots = typeof __propDef.slots;
12
+ export default class Chart extends SvelteComponentTyped<ChartProps, ChartEvents, ChartSlots> {
13
+ }
14
+ export {};
@@ -0,0 +1,42 @@
1
+ <script lang="ts">
2
+ import type { ExtensionProps } from "@lobb-js/studio";
3
+
4
+ import { onMount } from 'svelte';
5
+ import { Chart } from 'chart.js/auto';
6
+ import { merge } from 'lodash-es';
7
+
8
+ interface Props extends ExtensionProps {
9
+ input: any;
10
+ }
11
+
12
+ const { input }: Props = $props();
13
+
14
+ let canvas: HTMLCanvasElement;
15
+ let chart: Chart;
16
+
17
+ onMount(() => {
18
+ if (canvas) {
19
+ const ctx = canvas.getContext('2d');
20
+ if (ctx) {
21
+ chart = new Chart(
22
+ ctx,
23
+ merge(
24
+ {
25
+ options: {
26
+ responsive: true,
27
+ maintainAspectRatio: false
28
+ }
29
+ },
30
+ $state.snapshot(input)
31
+ )
32
+ );
33
+ }
34
+ }
35
+
36
+ return () => {
37
+ chart?.destroy();
38
+ };
39
+ });
40
+ </script>
41
+
42
+ <canvas bind:this={canvas}></canvas>
@@ -0,0 +1,14 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+ declare const __propDef: {
3
+ props: Record<string, never>;
4
+ events: {
5
+ [evt: string]: CustomEvent<any>;
6
+ };
7
+ slots: {};
8
+ };
9
+ export type ChartJsProps = typeof __propDef.props;
10
+ export type ChartJsEvents = typeof __propDef.events;
11
+ export type ChartJsSlots = typeof __propDef.slots;
12
+ export default class ChartJs extends SvelteComponentTyped<ChartJsProps, ChartJsEvents, ChartJsSlots> {
13
+ }
14
+ export {};
@@ -0,0 +1,36 @@
1
+ <script lang="ts">
2
+ import type { ExtensionProps } from "@lobb-js/studio";
3
+
4
+ interface Props extends ExtensionProps {
5
+ input: any;
6
+ }
7
+
8
+ const { input, utils }: Props = $props();
9
+
10
+ const { data } = input;
11
+ const { Table } = utils.components;
12
+
13
+ const columns = data.length
14
+ ? Object.keys(data[0]).map((column) => {
15
+ return {
16
+ id: column,
17
+ };
18
+ })
19
+ : null;
20
+ </script>
21
+
22
+ <div class="relative h-full w-full">
23
+ {#if columns}
24
+ <Table
25
+ {data}
26
+ {columns}
27
+ selectByColumn={null}
28
+ localSorting={true}
29
+ showLastRowBorder={true}
30
+ showLastColumnBorder={true}
31
+ unifiedBgColor="bg-background"
32
+ ></Table>
33
+ {:else}
34
+ <div class="flex justify-center items-center h-full">No Results</div>
35
+ {/if}
36
+ </div>
@@ -0,0 +1,14 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+ declare const __propDef: {
3
+ props: Record<string, never>;
4
+ events: {
5
+ [evt: string]: CustomEvent<any>;
6
+ };
7
+ slots: {};
8
+ };
9
+ export type TableProps = typeof __propDef.props;
10
+ export type TableEvents = typeof __propDef.events;
11
+ export type TableSlots = typeof __propDef.slots;
12
+ export default class Table extends SvelteComponentTyped<TableProps, TableEvents, TableSlots> {
13
+ }
14
+ export {};
@@ -0,0 +1,156 @@
1
+ <script lang="ts">
2
+ import type { ExtensionProps } from "@lobb-js/studio";
3
+ import type { DateRange } from "bits-ui";
4
+ import Chart from "./chart.svelte";
5
+
6
+ interface Props extends ExtensionProps {
7
+ reportId: string;
8
+ }
9
+
10
+ const props: Props = $props();
11
+ const { intlDate } = props.utils;
12
+ const {
13
+ SidebarTrigger,
14
+ CreateDetailViewButton,
15
+ Icons,
16
+ RangeCalendarButton,
17
+ } = props.utils.components;
18
+
19
+ let dateRangeValue: DateRange = $state({
20
+ start: intlDate
21
+ .today(intlDate.getLocalTimeZone())
22
+ .subtract({ days: 30 }),
23
+ end: intlDate.today(intlDate.getLocalTimeZone()),
24
+ });
25
+ let context = $derived({
26
+ dateRange: {
27
+ start: dateRangeValue.start?.toString(),
28
+ end: dateRangeValue.end?.toString(),
29
+ },
30
+ });
31
+
32
+ let report: any = $state(null);
33
+ let chartsPromise = $state(getCharts());
34
+
35
+ async function getCharts() {
36
+ const reportsResponse = await props.utils.lobb.findAll(
37
+ "reports_dashboards",
38
+ {
39
+ sort: "id",
40
+ filter: {
41
+ id: props.reportId,
42
+ },
43
+ },
44
+ );
45
+ report = (await reportsResponse.json()).data[0];
46
+ const chartsResponse = await props.utils.lobb.findAll(
47
+ "reports_charts",
48
+ {
49
+ filter: {
50
+ report_id: report.id,
51
+ },
52
+ },
53
+ );
54
+ return (await chartsResponse.json()).data;
55
+ }
56
+
57
+ async function reloadChart() {
58
+ chartsPromise = new Promise(() => {});
59
+ chartsPromise = Promise.resolve(await getCharts());
60
+ }
61
+ </script>
62
+
63
+ <div class="gridContainer relative h-full w-full flex-col overflow-auto">
64
+ {#await chartsPromise}
65
+ <div
66
+ class="flex flex-col gap-4 h-full w-full absolute justify-center items-center"
67
+ >
68
+ <Icons.LoaderCircle class="animate-spin opacity-50" size="50" />
69
+ <div class="flex flex-col items-center justify-center">
70
+ <div class="text-muted-foreground">
71
+ Loading the dashboard...
72
+ </div>
73
+ <div class="text-muted-foreground text-xs">
74
+ Loading all the charts of the selected dashboard
75
+ </div>
76
+ </div>
77
+ </div>
78
+ {:then charts}
79
+ <div
80
+ class="flex justify-between gap-2 overflow-auto border-b bg-background p-2"
81
+ >
82
+ <div class="flex gap-2">
83
+ <div class="mt-1">
84
+ <SidebarTrigger />
85
+ </div>
86
+ <div class="flex flex-col justify-center">
87
+ <h2 class="font-medium text-primary">{report.name}</h2>
88
+ <div class="text-xs text-muted-foreground">
89
+ {report.description}
90
+ </div>
91
+ </div>
92
+ </div>
93
+ <div class="flex gap-2 self-end">
94
+ <RangeCalendarButton bind:value={dateRangeValue} />
95
+ <CreateDetailViewButton
96
+ collectionName="reports_charts"
97
+ values={{
98
+ report_id: {
99
+ id: props.reportId,
100
+ name: report.name,
101
+ },
102
+ }}
103
+ variant="default"
104
+ class="h-7 px-3 text-xs font-normal"
105
+ Icon={Icons.Plus}
106
+ onSuccessfullSave={async () => await reloadChart()}
107
+ >
108
+ Create chart
109
+ </CreateDetailViewButton>
110
+ </div>
111
+ </div>
112
+ <div class="overflow-auto">
113
+ <div class="chartsGridContainer w-full flex-wrap gap-4 p-4">
114
+ {#if charts.length === 0}
115
+ <div
116
+ class="flex w-full flex-col items-center justify-center gap-4 p-10 text-muted-foreground"
117
+ >
118
+ <Icons.CircleSlash2 class="opacity-50" size="50" />
119
+ <div class="flex flex-col items-center justify-center">
120
+ <div>No charts available</div>
121
+ <div class="text-xs">
122
+ Create a new chart to fill this report page
123
+ </div>
124
+ </div>
125
+ </div>
126
+ {:else}
127
+ {#each charts as chart}
128
+ {#key context}
129
+ <Chart
130
+ chartRecord={chart}
131
+ onChartDeleted={async () => await reloadChart()}
132
+ onChartEdited={async () => await reloadChart()}
133
+ {context}
134
+ {...props}
135
+ />
136
+ {/key}
137
+ {/each}
138
+ {/if}
139
+ </div>
140
+ </div>
141
+ {/await}
142
+ </div>
143
+
144
+ <style>
145
+ .gridContainer {
146
+ display: grid;
147
+ grid-template-columns: 1fr;
148
+ grid-template-rows: auto 1fr;
149
+ }
150
+
151
+ .chartsGridContainer {
152
+ display: grid;
153
+ grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
154
+ grid-gap: 10px;
155
+ }
156
+ </style>
@@ -0,0 +1,14 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+ declare const __propDef: {
3
+ props: Record<string, never>;
4
+ events: {
5
+ [evt: string]: CustomEvent<any>;
6
+ };
7
+ slots: {};
8
+ };
9
+ export type ReportProps = typeof __propDef.props;
10
+ export type ReportEvents = typeof __propDef.events;
11
+ export type ReportSlots = typeof __propDef.slots;
12
+ export default class Report extends SvelteComponentTyped<ReportProps, ReportEvents, ReportSlots> {
13
+ }
14
+ export {};
@@ -0,0 +1,141 @@
1
+ <script lang="ts">
2
+ import type { ExtensionProps } from "@lobb-js/studio";
3
+ import type { Location } from "@wjfe/n-savant";
4
+ import Report from "./components/report.svelte";
5
+
6
+ const { utils, ...props }: ExtensionProps = $props();
7
+
8
+ let extensionPagePath = "/studio/extensions/reports/analytics";
9
+ let reportId: string | null = $derived(getReportID(utils.location));
10
+ let sideBarData: any = $state(null);
11
+ const {
12
+ CreateDetailViewButton,
13
+ UpdateDetailViewButton,
14
+ Sidebar,
15
+ SidebarTrigger,
16
+ Button,
17
+ } = utils.components;
18
+ const icons = utils.components.Icons;
19
+
20
+ getSideBarData();
21
+
22
+ async function getSideBarData(): Promise<void> {
23
+ const response = await utils.lobb.findAll("reports_dashboards", {
24
+ sort: "id",
25
+ filter: {},
26
+ });
27
+ const allReports = (await response.json()).data;
28
+
29
+ const localSideBarData: any = [];
30
+ for (let index = 0; index < allReports.length; index++) {
31
+ const report = allReports[index];
32
+ localSideBarData.push({
33
+ name: report.name,
34
+ icon: icons.FileChartColumn,
35
+ onclick: () => {
36
+ utils.location.navigate(
37
+ `${extensionPagePath}/${report.id}`,
38
+ );
39
+ },
40
+ meta: {
41
+ reportId: report.id,
42
+ },
43
+ });
44
+ }
45
+
46
+ sideBarData = localSideBarData;
47
+ }
48
+
49
+ async function handleReportDelete(recordId: any) {
50
+ const result = await utils.showDialog(
51
+ "Are you sure?",
52
+ "This will delete the report you selected.",
53
+ );
54
+ if (result) {
55
+ await utils.lobb.deleteOne("reports_dashboards", recordId);
56
+ }
57
+ }
58
+
59
+ function getReportID(location: Location) {
60
+ return location.url.pathname.split("/")[5];
61
+ }
62
+ </script>
63
+
64
+ <Sidebar title="Reports" data={sideBarData}>
65
+ {#snippet belowSearch()}
66
+ <div class="p-2 pb-0">
67
+ <CreateDetailViewButton
68
+ collectionName="reports_dashboards"
69
+ variant="outline"
70
+ class="h-7 w-full px-3 text-xs font-normal"
71
+ Icon={icons.Plus}
72
+ onSuccessfullSave={async (record: any) => {
73
+ await getSideBarData();
74
+ reportId = record.id;
75
+ }}
76
+ >
77
+ Create a report
78
+ </CreateDetailViewButton>
79
+ </div>
80
+ {/snippet}
81
+ {#snippet elementRightSide(item: any)}
82
+ {#if item.meta}
83
+ {@const reportId = item.meta.reportId}
84
+ <div>
85
+ <UpdateDetailViewButton
86
+ collectionName="reports_dashboards"
87
+ recordId={reportId}
88
+ variant="ghost"
89
+ class="h-6 w-6 text-muted-foreground hover:bg-transparent"
90
+ Icon={icons.Pencil}
91
+ onSuccessfullSave={async () => {
92
+ await getSideBarData();
93
+ }}
94
+ ></UpdateDetailViewButton>
95
+ <Button
96
+ class="h-6 w-6 text-muted-foreground hover:bg-transparent"
97
+ variant="ghost"
98
+ size="icon"
99
+ Icon={icons.Trash}
100
+ onclick={async () => {
101
+ await handleReportDelete(reportId);
102
+ await getSideBarData();
103
+ }}
104
+ ></Button>
105
+ </div>
106
+ {/if}
107
+ {/snippet}
108
+ <div class="relative h-full w-full bg-muted">
109
+ {#if reportId}
110
+ {#key reportId}
111
+ <Report {utils} {reportId} {...props} />
112
+ {/key}
113
+ {:else}
114
+ <div
115
+ class="flex h-full w-full flex-col items-center justify-center gap-4 text-muted-foreground"
116
+ >
117
+ <icons.CircleSlash2 class="opacity-50" size="50" />
118
+ <div class="flex flex-col items-center justify-center">
119
+ <div>No report selected</div>
120
+ <div class="text-xs">
121
+ Please select a report to view the details or create a
122
+ new one
123
+ </div>
124
+ </div>
125
+ <CreateDetailViewButton
126
+ collectionName="reports_dashboards"
127
+ variant="default"
128
+ class="h-7 px-3 text-xs font-normal"
129
+ Icon={icons.Plus}
130
+ onSuccessfullSave={async (record: any) =>
131
+ utils.location.navigate(`?report=${record.id}`)}
132
+ >
133
+ Create a report
134
+ </CreateDetailViewButton>
135
+ </div>
136
+ <div class="absolute top-0 left-0 p-2.5">
137
+ <SidebarTrigger />
138
+ </div>
139
+ {/if}
140
+ </div>
141
+ </Sidebar>
@@ -0,0 +1,14 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+ declare const __propDef: {
3
+ props: Record<string, never>;
4
+ events: {
5
+ [evt: string]: CustomEvent<any>;
6
+ };
7
+ slots: {};
8
+ };
9
+ export type IndexProps = typeof __propDef.props;
10
+ export type IndexEvents = typeof __propDef.events;
11
+ export type IndexSlots = typeof __propDef.slots;
12
+ export default class Index extends SvelteComponentTyped<IndexProps, IndexEvents, IndexSlots> {
13
+ }
14
+ export {};
File without changes
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ // place files you want to import through the `$lib` alias in this folder.
@@ -0,0 +1,12 @@
1
+ import { type ClassValue } from "clsx";
2
+ export declare function cn(...inputs: ClassValue[]): string;
3
+ export type WithoutChild<T> = T extends {
4
+ child?: any;
5
+ } ? Omit<T, "child"> : T;
6
+ export type WithoutChildren<T> = T extends {
7
+ children?: any;
8
+ } ? Omit<T, "children"> : T;
9
+ export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>;
10
+ export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & {
11
+ ref?: U | null;
12
+ };
@@ -0,0 +1,5 @@
1
+ import { clsx } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+ export function cn(...inputs) {
4
+ return twMerge(clsx(inputs));
5
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,17 @@
1
+ import { test, expect } from "@playwright/test";
2
+ const ANALYTICS_URL = "/studio/extensions/reports/analytics";
3
+ test.describe("Analytics page", () => {
4
+ test.beforeEach(async ({ page }) => {
5
+ await page.goto(ANALYTICS_URL);
6
+ });
7
+ test("shows the analytics page", async ({ page }) => {
8
+ await expect(page).toHaveURL(new RegExp(ANALYTICS_URL));
9
+ });
10
+ test("shows empty state when no report is selected", async ({ page }) => {
11
+ await expect(page.getByText("No report selected")).toBeVisible();
12
+ await expect(page.getByText("Please select a report to view the details or create a new one")).toBeVisible();
13
+ });
14
+ test("shows create a report button in empty state", async ({ page }) => {
15
+ await expect(page.getByRole("button", { name: "Create a report" }).first()).toBeVisible();
16
+ });
17
+ });
@@ -0,0 +1 @@
1
+ {"type": "commonjs"}
@@ -0,0 +1,27 @@
1
+ const { defineConfig, devices } = require("@playwright/test");
2
+
3
+ module.exports = defineConfig({
4
+ testDir: __dirname,
5
+ testMatch: "**/*.spec.ts",
6
+ fullyParallel: true,
7
+ retries: 0,
8
+ expect: { timeout: 15000 },
9
+ use: {
10
+ baseURL: "http://localhost:3000",
11
+ trace: "on-first-retry",
12
+ headless: true,
13
+ },
14
+ projects: [
15
+ {
16
+ name: "chromium",
17
+ use: { ...devices["Desktop Chrome"] },
18
+ },
19
+ ],
20
+ // Automatically start the dev server before running tests
21
+ webServer: {
22
+ command: "bun run dev",
23
+ url: "http://localhost:3000/studio",
24
+ reuseExistingServer: true,
25
+ cwd: "../../../../", // packages/reports-ext root
26
+ },
27
+ });
@@ -0,0 +1,2 @@
1
+ declare const _exports: import("@playwright/test").Config<import("@playwright/test").PlaywrightTestOptions & {}, import("@playwright/test").PlaywrightWorkerOptions & {}>;
2
+ export = _exports;
package/package.json CHANGED
@@ -1,11 +1,15 @@
1
1
  {
2
2
  "name": "@lobb-js/lobb-ext-reports",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "license": "AGPL-3.0-only",
5
5
  "type": "module",
6
6
  "publishConfig": {
7
7
  "access": "public"
8
8
  },
9
+ "files": [
10
+ "extensions",
11
+ "dist"
12
+ ],
9
13
  "exports": {
10
14
  ".": "./extensions/reports/index.ts",
11
15
  "./studio": {
@@ -30,7 +34,7 @@
30
34
  "build:studio": "vite build"
31
35
  },
32
36
  "dependencies": {
33
- "@lobb-js/core": "0.13.1",
37
+ "@lobb-js/core": "^0.13.3",
34
38
  "chart.js": "^4.4.8",
35
39
  "hono": "^4.7.0",
36
40
  "lodash-es": "^4.17.21",
@@ -39,7 +43,7 @@
39
43
  "devDependencies": {
40
44
  "@faker-js/faker": "^9.6.0",
41
45
  "@playwright/test": "^1.58.2",
42
- "@lobb-js/studio": "0.7.1",
46
+ "@lobb-js/studio": "^0.7.3",
43
47
  "@lucide/svelte": "^0.563.1",
44
48
  "@sveltejs/adapter-node": "^5.5.4",
45
49
  "@sveltejs/kit": "^2.55.0",
package/.dockerignore DELETED
@@ -1,7 +0,0 @@
1
- node_modules
2
- .git
3
- .gitignore
4
- *.md
5
- .vscode
6
- studio/node_modules
7
- studio/dist
package/.env.example DELETED
@@ -1,10 +0,0 @@
1
- # Database
2
- DATABASE_HOST=localhost
3
- DATABASE_PORT=5432
4
- DATABASE_USER=test
5
- DATABASE_PASSWORD=test
6
- DATABASE_NAME=reports-ext
7
-
8
- # Web Server
9
- WEB_SERVER_HOST=0.0.0.0
10
- WEB_SERVER_PORT=3000
@@ -1,5 +0,0 @@
1
- {
2
- "deno.disablePaths": [
3
- "studio"
4
- ]
5
- }
package/CHANGELOG.md DELETED
@@ -1,132 +0,0 @@
1
- # Changelog
2
- All notable changes to this project will be documented in this file. See [conventional commits](https://www.conventionalcommits.org/) for commit guidelines.
3
-
4
- - - -
5
- ## reports-ext@0.3.1 - 2026-03-28
6
- #### Bug Fixes
7
- - adding readme to all packages - (3a9264a) - malik ben
8
- #### Miscellaneous Chores
9
- - add publishConfig and fix ext packages for npm publishing - (49747e9) - malik ben
10
-
11
- - - -
12
-
13
- ## reports-ext@0.3.0 - 2026-03-28
14
- #### Features
15
- - (**reports-ext**) migrate to Bun - (3d9f927) - malik ben
16
- - (**reports-ext,storage-ext**) convert studio from Svelte SPA to SvelteKit - (3cd7f63) - malik ben
17
- - replace hasDashboardExtension with virtual:lobb-studio-extensions module - (437cb2e) - malik ben
18
- #### Bug Fixes
19
- - add /studio subpath exports to ext packages, update studio pages to use them - (c0b9a82) - malik ben
20
- - untrack .svelte-kit from mindhar, fix process leak and $lib imports, update ai sdk versions - (017f784) - malik ben
21
- - chaning $lib to relative path - (c6d9e8f) - malik ben
22
- #### Miscellaneous Chores
23
- - (**reports-ext**) move extension logic and tests to extensions/reports, flatten studio, add Playwright tests - (f814cbf) - malik ben
24
- - (**version**) 0.25.2 - (a62acb9) - Cocogitto Bot
25
- - (**version**) 0.25.1 - (afe7e69) - Cocogitto Bot
26
- - (**version**) 0.25.0 - (77a383c) - Cocogitto Bot
27
- - (**version**) 0.24.0 - (a8cb605) - Cocogitto Bot
28
- - (**version**) 0.23.0 - (60f357e) - Cocogitto Bot
29
- - (**version**) 0.22.0 - (6510e32) - Cocogitto Bot
30
- - (**version**) 0.21.0 - (c973aa9) - Cocogitto Bot
31
- - (**version**) 0.20.0 - (06cc303) - Cocogitto Bot
32
- - add dev:studio/build:studio scripts, fix Dockerfiles, remove --build flag - (1595975) - malik ben
33
- - add prepublish/postpublish scripts to extension packages for standalone compatibility - (4d6108f) - malik ben
34
- - centralize studio app.css in @lobb-js/studio package, remove local copies - (05192dc) - malik ben
35
- - rename @lobb/ scope to @lobb-js/ across all packages and apps - (cce4ce0) - malik ben
36
- - add start/build scripts and gitignore build dir across all projects - (58f539d) - malik ben
37
- - update CLAUDE.md to enforce no-commit-without-explicit-instruction rule - (6d63a42) - malik ben
38
- - remove all deno.json and deno.lock files from the monorepo - (84ca5d4) - malik ben
39
- - replace workspace:* with exact versions in all package.json files - (74fbdb7) - malik ben
40
- - rename __studio to studio and remove unused studio dirs - (77fb932) - malik ben
41
- - rename studio directory to __studio for faker-ext, llm-ext, mail-ext, reports-ext, storage-ext - (47e754a) - malik ben
42
- - bump deno docker image to 2.6.6 across all Dockerfiles - (a2865b4) - malik ben
43
-
44
- - - -
45
-
46
- ## reports-ext@0.2.0 - 2026-03-01
47
- #### Features
48
- - (**mindhar**) add storage extension integration - (bca6368) - malik ben
49
- #### Miscellaneous Chores
50
- - (**version**) 0.18.0 - (efc553f) - Cocogitto Bot
51
- - (**version**) 0.17.0 - (4174f0c) - Cocogitto Bot
52
- - (**version**) 0.16.0 - (9508655) - Cocogitto Bot
53
- - (**version**) 0.14.11 - (ad92b61) - Cocogitto Bot
54
- - (**version**) 0.14.8 - (0e6c1cb) - Cocogitto Bot
55
-
56
- - - -
57
-
58
- ## reports-ext@0.1.34 - 2026-02-22
59
- #### Bug Fixes
60
- - coggito publishing packages order fix - (573c75e) - malik ben
61
- #### Miscellaneous Chores
62
- - (**version**) 0.14.3 - (76abe92) - Cocogitto Bot
63
-
64
- - - -
65
-
66
- ## reports-ext@0.1.33 - 2026-02-22
67
- #### Bug Fixes
68
- - made the collectionService become an property in the main lobb object - (146e4cb) - malik ben
69
-
70
- - - -
71
-
72
- ## reports-ext@0.1.32 - 2026-02-21
73
- #### Bug Fixes
74
- - using default export instead of named export for extensions - (37dd485) - malik ben
75
- #### Refactoring
76
- - moving ui_input into the ui property of the field - (8a9edf0) - Malik Najjar
77
-
78
- - - -
79
-
80
- ## reports-ext@0.1.31 - 2026-02-20
81
- #### Bug Fixes
82
- - fix reports extension race condition - (6dd72ba) - Malik Najjar
83
- #### Miscellaneous Chores
84
- - (**version**) 0.13.2 - (39b0145) - Cocogitto Bot
85
-
86
- - - -
87
-
88
- ## reports-ext@0.1.30 - 2026-02-19
89
- #### Bug Fixes
90
- - concurent racing fix - (0eeda5f) - malik ben
91
-
92
- - - -
93
-
94
- ## reports-ext@0.1.29 - 2026-02-19
95
- #### Bug Fixes
96
- - adding testing logs - (69ab047) - malik ben
97
- #### Miscellaneous Chores
98
- - (**version**) 0.12.3 - (cd06fc0) - Cocogitto Bot
99
- - (**version**) 0.12.2 - (35b2ff3) - Cocogitto Bot
100
- - (**version**) 0.12.1 - (c548105) - Cocogitto Bot
101
- - adding testing logs - (ef42d92) - malik ben
102
-
103
- - - -
104
-
105
- ## reports-ext@0.1.28 - 2026-02-19
106
- #### Bug Fixes
107
- - making the reports work - (1b87561) - Malik Najjar
108
- #### Miscellaneous Chores
109
- - (**version**) 0.11.1 - (659ebd3) - Cocogitto Bot
110
- - (**version**) 0.11.0 - (3f4f47e) - Cocogitto Bot
111
- - (**version**) 0.10.0 - (5d79b6e) - Cocogitto Bot
112
- - (**version**) 0.8.0 - (fdee7ca) - Cocogitto Bot
113
-
114
- - - -
115
-
116
- ## reports-ext@0.1.27 - 2026-02-15
117
- #### Bug Fixes
118
- - fix deno publish issue - (e8dcc4f) - malik ben
119
- - issue fix - (63d66d3) - malik ben
120
- #### Miscellaneous Chores
121
- - (**version**) 0.5.5 - (d4dedeb) - Cocogitto Bot
122
- - (**version**) 0.5.4 - (1ca3970) - Cocogitto Bot
123
- - (**version**) 0.5.3 - (dcdb9cb) - Cocogitto Bot
124
- - (**version**) 0.5.2 - (aa66e29) - Cocogitto Bot
125
- - (**version**) 0.5.1 - (41b7c35) - Cocogitto Bot
126
- - (**version**) 0.5.0 - (af63147) - Cocogitto Bot
127
- - (**version**) 0.4.4 - (eaed3b4) - Cocogitto Bot
128
- - (**version**) 0.4.3 - (ea9ec49) - Cocogitto Bot
129
-
130
- - - -
131
-
132
- Changelog generated by [cocogitto](https://github.com/cocogitto/cocogitto).
package/lobb.ts DELETED
@@ -1,55 +0,0 @@
1
- import { Lobb } from "@lobb-js/core";
2
- import { extension } from "./extensions/reports/index.ts";
3
-
4
- Lobb.init({
5
- project: {
6
- name: "Reports Extension Test",
7
- force_sync: true,
8
- },
9
- database: {
10
- host: "localhost",
11
- port: 5432,
12
- username: "test",
13
- password: "test",
14
- database: "reports_ext_dev",
15
- },
16
- web_server: {
17
- host: "0.0.0.0",
18
- port: 3000,
19
- cors: {
20
- origin: "*",
21
- },
22
- },
23
- extensions: [
24
- extension(),
25
- ],
26
- collections: {
27
- donations: {
28
- indexes: {},
29
- fields: {
30
- id: {
31
- type: "integer",
32
- },
33
- donor_name: {
34
- type: "string",
35
- length: 255,
36
- validators: {
37
- required: true,
38
- },
39
- },
40
- amount: {
41
- type: "decimal",
42
- validators: {
43
- required: true,
44
- },
45
- },
46
- created_at: {
47
- type: "date",
48
- pre_processors: {
49
- default: "{{ now }}",
50
- },
51
- },
52
- },
53
- },
54
- },
55
- });
@@ -1,12 +0,0 @@
1
- #!/bin/bash
2
-
3
- # Postpublish script for @lobb-js/lobb-ext-reports
4
- # Reverts package.json exports and cleans dist to avoid uncommitted changes
5
-
6
- echo "📝 Reverting package.json exports to development mode..."
7
- jq '."exports"["./studio"] = "./extensions/reports/studio/index.ts"' package.json > package.json.tmp && mv package.json.tmp package.json
8
-
9
- echo "🧹 Cleaning dist directory..."
10
- rm -rf dist
11
-
12
- echo "✅ Postpublish complete"
@@ -1,17 +0,0 @@
1
- #!/bin/bash
2
-
3
- # Prepublish script for @lobb-js/lobb-ext-reports
4
- # Builds the studio package and updates exports for publishing
5
-
6
- echo "📦 Building studio package..."
7
- bun run package
8
-
9
- if [ $? -ne 0 ]; then
10
- echo "❌ Build failed"
11
- exit 1
12
- fi
13
-
14
- echo "📝 Updating package.json exports for publishing..."
15
- jq '."exports"["./studio"] = {"svelte": "./dist/index.js", "types": "./dist/index.d.ts"}' package.json > package.json.tmp && mv package.json.tmp package.json
16
-
17
- echo "✅ Prepublish complete"
package/studio/app.html DELETED
@@ -1,12 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8" />
5
- <link rel="icon" href="%sveltekit.assets%/vite.svg" />
6
- <meta name="viewport" content="width=device-width, initial-scale=1" />
7
- %sveltekit.head%
8
- </head>
9
- <body data-sveltekit-preload-data="hover">
10
- <div style="display: contents">%sveltekit.body%</div>
11
- </body>
12
- </html>
@@ -1,7 +0,0 @@
1
- <script lang="ts">
2
- import "@lobb-js/studio/app.css";
3
-
4
- let { children } = $props();
5
- </script>
6
-
7
- {@render children()}
@@ -1 +0,0 @@
1
- export const ssr = false;
@@ -1,6 +0,0 @@
1
- <script lang="ts">
2
- import { Studio } from "@lobb-js/studio";
3
- import { env } from "$env/dynamic/public";
4
- </script>
5
-
6
- <Studio lobbUrl={env.PUBLIC_LOBB_URL} />
package/svelte.config.js DELETED
@@ -1,24 +0,0 @@
1
- import adapter from '@sveltejs/adapter-node';
2
-
3
- /** @type {import('@sveltejs/kit').Config} */
4
- const config = {
5
- kit: {
6
- adapter: adapter(),
7
- paths: {
8
- base: '/studio'
9
- },
10
- files: {
11
- lib: 'studio/lib',
12
- routes: 'studio/routes',
13
- appTemplate: 'studio/app.html',
14
- assets: 'public',
15
- hooks: {
16
- server: 'studio/hooks.server',
17
- client: 'studio/hooks.client',
18
- },
19
- params: 'studio/params',
20
- }
21
- }
22
- };
23
-
24
- export default config;
package/todo.md DELETED
@@ -1,11 +0,0 @@
1
- # high priority
2
-
3
- - make it have two collections. `reports` and `charts`
4
- - create the endpoint that runs the query of the report
5
- - you would have global configuration that can effect all reports. or local
6
- configuration that effects a single report
7
- - there will be a sidebar with created reports. reports is a page that contains
8
- multiple widgets of reports
9
- - some widgets can be expanded to see more details and control over it
10
- - support realtime by specifying a script that implements it efficienlty instead
11
- of executing the hole query each time
package/tsconfig.app.json DELETED
@@ -1,27 +0,0 @@
1
- {
2
- "extends": "@tsconfig/svelte/tsconfig.json",
3
- "compilerOptions": {
4
- "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
5
- "target": "ES2022",
6
- "useDefineForClassFields": true,
7
- "module": "ESNext",
8
- "types": ["svelte", "vite/client"],
9
- "noEmit": true,
10
- "allowArbitraryExtensions": true,
11
- /**
12
- * Typecheck JS in `.svelte` and `.js` files by default.
13
- * Disable checkJs if you'd like to use dynamic types in JS.
14
- * Note that setting allowJs false does not prevent the use
15
- * of JS in `.svelte` files.
16
- */
17
- "allowJs": true,
18
- "checkJs": true,
19
- "moduleDetection": "force",
20
- "baseUrl": ".",
21
- "paths": {
22
- "$lib": ["./studio/lib"],
23
- "$lib/*": ["./studio/lib/*"]
24
- }
25
- },
26
- "include": ["studio/**/*.ts", "studio/**/*.js", "studio/**/*.svelte"]
27
- }
package/tsconfig.json DELETED
@@ -1,13 +0,0 @@
1
- {
2
- "extends": "./.svelte-kit/tsconfig.json",
3
- "compilerOptions": {
4
- "allowJs": true,
5
- "checkJs": true,
6
- "esModuleInterop": true,
7
- "forceConsistentCasingInFileNames": true,
8
- "resolveJsonModule": true,
9
- "skipLibCheck": true,
10
- "sourceMap": true,
11
- "strict": true
12
- }
13
- }
@@ -1,26 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4
- "target": "ES2023",
5
- "lib": ["ES2023"],
6
- "module": "ESNext",
7
- "types": ["node"],
8
- "skipLibCheck": true,
9
-
10
- /* Bundler mode */
11
- "moduleResolution": "bundler",
12
- "allowImportingTsExtensions": true,
13
- "verbatimModuleSyntax": true,
14
- "moduleDetection": "force",
15
- "noEmit": true,
16
-
17
- /* Linting */
18
- "strict": true,
19
- "noUnusedLocals": true,
20
- "noUnusedParameters": true,
21
- "erasableSyntaxOnly": true,
22
- "noFallthroughCasesInSwitch": true,
23
- "noUncheckedSideEffectImports": true
24
- },
25
- "include": ["vite.config.ts"]
26
- }
package/vite.config.ts DELETED
@@ -1,8 +0,0 @@
1
- import { sveltekit } from '@sveltejs/kit/vite';
2
- import { defineConfig } from 'vite';
3
- import tailwindcss from "@tailwindcss/vite";
4
- import { lobbStudioPlugins } from '@lobb-js/studio/vite-plugins';
5
-
6
- export default defineConfig({
7
- plugins: [tailwindcss(), sveltekit(), ...lobbStudioPlugins()],
8
- });