@lobb-js/lobb-ext-reports 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +8 -4
  25. package/.dockerignore +0 -7
  26. package/.env.example +0 -10
  27. package/.vscode/settings.json +0 -5
  28. package/CHANGELOG.md +0 -141
  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.2",
3
+ "version": "0.4.0",
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": {
@@ -19,7 +23,7 @@
19
23
  "test:studio": "bun --bun playwright test --config extensions/reports/studio/tests/playwright.config.cjs",
20
24
  "postinstall": "bun --bun playwright install chromium",
21
25
  "dev": "bun run lobb.ts",
22
- "start": "LOBB_MODE=prod bun run lobb.ts",
26
+ "start": "bun run lobb.ts",
23
27
  "prepare": "svelte-kit sync || echo ''",
24
28
  "preview": "vite preview",
25
29
  "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
@@ -30,7 +34,7 @@
30
34
  "build:studio": "vite build"
31
35
  },
32
36
  "dependencies": {
33
- "@lobb-js/core": "^0.13.3",
37
+ "@lobb-js/core": "^0.14.0",
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.2",
46
+ "@lobb-js/studio": "^0.8.0",
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,141 +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.2 - 2026-03-28
6
- #### Bug Fixes
7
- - update readme to trigger publish - (cf1e896) - malik ben
8
- - update READMEs to trigger republish of all packages - (2bd145d) - malik ben
9
- #### Miscellaneous Chores
10
- - (**version**) 0.26.2 - (1907933) - Cocogitto Bot
11
-
12
- - - -
13
-
14
- ## reports-ext@0.3.1 - 2026-03-28
15
- #### Bug Fixes
16
- - adding readme to all packages - (3a9264a) - malik ben
17
- #### Miscellaneous Chores
18
- - add publishConfig and fix ext packages for npm publishing - (49747e9) - malik ben
19
-
20
- - - -
21
-
22
- ## reports-ext@0.3.0 - 2026-03-28
23
- #### Features
24
- - (**reports-ext**) migrate to Bun - (3d9f927) - malik ben
25
- - (**reports-ext,storage-ext**) convert studio from Svelte SPA to SvelteKit - (3cd7f63) - malik ben
26
- - replace hasDashboardExtension with virtual:lobb-studio-extensions module - (437cb2e) - malik ben
27
- #### Bug Fixes
28
- - add /studio subpath exports to ext packages, update studio pages to use them - (c0b9a82) - malik ben
29
- - untrack .svelte-kit from mindhar, fix process leak and $lib imports, update ai sdk versions - (017f784) - malik ben
30
- - chaning $lib to relative path - (c6d9e8f) - malik ben
31
- #### Miscellaneous Chores
32
- - (**reports-ext**) move extension logic and tests to extensions/reports, flatten studio, add Playwright tests - (f814cbf) - malik ben
33
- - (**version**) 0.25.2 - (a62acb9) - Cocogitto Bot
34
- - (**version**) 0.25.1 - (afe7e69) - Cocogitto Bot
35
- - (**version**) 0.25.0 - (77a383c) - Cocogitto Bot
36
- - (**version**) 0.24.0 - (a8cb605) - Cocogitto Bot
37
- - (**version**) 0.23.0 - (60f357e) - Cocogitto Bot
38
- - (**version**) 0.22.0 - (6510e32) - Cocogitto Bot
39
- - (**version**) 0.21.0 - (c973aa9) - Cocogitto Bot
40
- - (**version**) 0.20.0 - (06cc303) - Cocogitto Bot
41
- - add dev:studio/build:studio scripts, fix Dockerfiles, remove --build flag - (1595975) - malik ben
42
- - add prepublish/postpublish scripts to extension packages for standalone compatibility - (4d6108f) - malik ben
43
- - centralize studio app.css in @lobb-js/studio package, remove local copies - (05192dc) - malik ben
44
- - rename @lobb/ scope to @lobb-js/ across all packages and apps - (cce4ce0) - malik ben
45
- - add start/build scripts and gitignore build dir across all projects - (58f539d) - malik ben
46
- - update CLAUDE.md to enforce no-commit-without-explicit-instruction rule - (6d63a42) - malik ben
47
- - remove all deno.json and deno.lock files from the monorepo - (84ca5d4) - malik ben
48
- - replace workspace:* with exact versions in all package.json files - (74fbdb7) - malik ben
49
- - rename __studio to studio and remove unused studio dirs - (77fb932) - malik ben
50
- - rename studio directory to __studio for faker-ext, llm-ext, mail-ext, reports-ext, storage-ext - (47e754a) - malik ben
51
- - bump deno docker image to 2.6.6 across all Dockerfiles - (a2865b4) - malik ben
52
-
53
- - - -
54
-
55
- ## reports-ext@0.2.0 - 2026-03-01
56
- #### Features
57
- - (**mindhar**) add storage extension integration - (bca6368) - malik ben
58
- #### Miscellaneous Chores
59
- - (**version**) 0.18.0 - (efc553f) - Cocogitto Bot
60
- - (**version**) 0.17.0 - (4174f0c) - Cocogitto Bot
61
- - (**version**) 0.16.0 - (9508655) - Cocogitto Bot
62
- - (**version**) 0.14.11 - (ad92b61) - Cocogitto Bot
63
- - (**version**) 0.14.8 - (0e6c1cb) - Cocogitto Bot
64
-
65
- - - -
66
-
67
- ## reports-ext@0.1.34 - 2026-02-22
68
- #### Bug Fixes
69
- - coggito publishing packages order fix - (573c75e) - malik ben
70
- #### Miscellaneous Chores
71
- - (**version**) 0.14.3 - (76abe92) - Cocogitto Bot
72
-
73
- - - -
74
-
75
- ## reports-ext@0.1.33 - 2026-02-22
76
- #### Bug Fixes
77
- - made the collectionService become an property in the main lobb object - (146e4cb) - malik ben
78
-
79
- - - -
80
-
81
- ## reports-ext@0.1.32 - 2026-02-21
82
- #### Bug Fixes
83
- - using default export instead of named export for extensions - (37dd485) - malik ben
84
- #### Refactoring
85
- - moving ui_input into the ui property of the field - (8a9edf0) - Malik Najjar
86
-
87
- - - -
88
-
89
- ## reports-ext@0.1.31 - 2026-02-20
90
- #### Bug Fixes
91
- - fix reports extension race condition - (6dd72ba) - Malik Najjar
92
- #### Miscellaneous Chores
93
- - (**version**) 0.13.2 - (39b0145) - Cocogitto Bot
94
-
95
- - - -
96
-
97
- ## reports-ext@0.1.30 - 2026-02-19
98
- #### Bug Fixes
99
- - concurent racing fix - (0eeda5f) - malik ben
100
-
101
- - - -
102
-
103
- ## reports-ext@0.1.29 - 2026-02-19
104
- #### Bug Fixes
105
- - adding testing logs - (69ab047) - malik ben
106
- #### Miscellaneous Chores
107
- - (**version**) 0.12.3 - (cd06fc0) - Cocogitto Bot
108
- - (**version**) 0.12.2 - (35b2ff3) - Cocogitto Bot
109
- - (**version**) 0.12.1 - (c548105) - Cocogitto Bot
110
- - adding testing logs - (ef42d92) - malik ben
111
-
112
- - - -
113
-
114
- ## reports-ext@0.1.28 - 2026-02-19
115
- #### Bug Fixes
116
- - making the reports work - (1b87561) - Malik Najjar
117
- #### Miscellaneous Chores
118
- - (**version**) 0.11.1 - (659ebd3) - Cocogitto Bot
119
- - (**version**) 0.11.0 - (3f4f47e) - Cocogitto Bot
120
- - (**version**) 0.10.0 - (5d79b6e) - Cocogitto Bot
121
- - (**version**) 0.8.0 - (fdee7ca) - Cocogitto Bot
122
-
123
- - - -
124
-
125
- ## reports-ext@0.1.27 - 2026-02-15
126
- #### Bug Fixes
127
- - fix deno publish issue - (e8dcc4f) - malik ben
128
- - issue fix - (63d66d3) - malik ben
129
- #### Miscellaneous Chores
130
- - (**version**) 0.5.5 - (d4dedeb) - Cocogitto Bot
131
- - (**version**) 0.5.4 - (1ca3970) - Cocogitto Bot
132
- - (**version**) 0.5.3 - (dcdb9cb) - Cocogitto Bot
133
- - (**version**) 0.5.2 - (aa66e29) - Cocogitto Bot
134
- - (**version**) 0.5.1 - (41b7c35) - Cocogitto Bot
135
- - (**version**) 0.5.0 - (af63147) - Cocogitto Bot
136
- - (**version**) 0.4.4 - (eaed3b4) - Cocogitto Bot
137
- - (**version**) 0.4.3 - (ea9ec49) - Cocogitto Bot
138
-
139
- - - -
140
-
141
- 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
- });