@lobb-js/lobb-ext-reports 0.5.0 → 0.6.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.
- package/dist/lib/components/gridStack.svelte +12 -0
- package/dist/lib/components/pages/reports/components/chart.svelte +17 -6
- package/dist/lib/components/pages/reports/components/exportButton.svelte +57 -0
- package/dist/lib/components/pages/reports/components/exportButton.svelte.d.ts +14 -0
- package/dist/lib/components/pages/reports/components/report.svelte +17 -37
- package/extensions/reports/studio/lib/components/gridStack.svelte +12 -0
- package/extensions/reports/studio/lib/components/pages/reports/components/chart.svelte +17 -6
- package/extensions/reports/studio/lib/components/pages/reports/components/exportButton.svelte +57 -0
- package/extensions/reports/studio/lib/components/pages/reports/components/report.svelte +17 -37
- package/package.json +4 -2
|
@@ -102,4 +102,16 @@
|
|
|
102
102
|
background: transparent;
|
|
103
103
|
padding: 0.5rem;
|
|
104
104
|
}
|
|
105
|
+
|
|
106
|
+
:global(.grid-stack-placeholder > .placeholder-content) {
|
|
107
|
+
position: absolute !important;
|
|
108
|
+
width: auto !important;
|
|
109
|
+
border: 2px dashed color-mix(in oklab, var(--muted-foreground) 25%, transparent) !important;
|
|
110
|
+
border-radius: 0.375rem !important;
|
|
111
|
+
background-color: rgba(0, 0, 0, 0.06) !important;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
:global(.dark .grid-stack-placeholder > .placeholder-content) {
|
|
115
|
+
background-color: rgba(255, 255, 255, 0.06) !important;
|
|
116
|
+
}
|
|
105
117
|
</style>
|
|
@@ -8,20 +8,27 @@
|
|
|
8
8
|
chartRecord: any;
|
|
9
9
|
onChartDeleted?: () => Promise<void>;
|
|
10
10
|
onChartEdited?: () => Promise<void>;
|
|
11
|
-
context?: Record<string, any>;
|
|
12
11
|
}
|
|
13
12
|
|
|
14
13
|
const {
|
|
15
14
|
chartRecord,
|
|
16
15
|
onChartDeleted,
|
|
17
16
|
onChartEdited,
|
|
18
|
-
context,
|
|
19
17
|
...props
|
|
20
18
|
}: Props = $props();
|
|
21
19
|
|
|
22
20
|
const utils = props.utils;
|
|
23
21
|
const { UpdateDetailViewButton, Button } = utils.components;
|
|
24
22
|
const icons = utils.components.Icons;
|
|
23
|
+
|
|
24
|
+
function findCustomChartComponent(type: string): any {
|
|
25
|
+
const extensions = utils.ctx?.extensions ?? {};
|
|
26
|
+
for (const ext of Object.values(extensions) as any[]) {
|
|
27
|
+
const comp = ext.components?.[`reports.chart.${type}`];
|
|
28
|
+
if (comp) return comp;
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
25
32
|
let dataPromise = $state(getChartData());
|
|
26
33
|
|
|
27
34
|
async function getChartData() {
|
|
@@ -30,7 +37,6 @@
|
|
|
30
37
|
chartRecord.id,
|
|
31
38
|
{
|
|
32
39
|
action: "run_query",
|
|
33
|
-
context: JSON.stringify(context),
|
|
34
40
|
},
|
|
35
41
|
);
|
|
36
42
|
|
|
@@ -117,9 +123,14 @@
|
|
|
117
123
|
{:else if data.type === "metric"}
|
|
118
124
|
<Metric input={data.input} />
|
|
119
125
|
{:else}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
126
|
+
{@const CustomChart = findCustomChartComponent(data.type)}
|
|
127
|
+
{#if CustomChart}
|
|
128
|
+
<CustomChart input={data.input} {...props} />
|
|
129
|
+
{:else}
|
|
130
|
+
<div class="text-muted-foreground">
|
|
131
|
+
The ({data.type}) chart type is unsupported
|
|
132
|
+
</div>
|
|
133
|
+
{/if}
|
|
123
134
|
{/if}
|
|
124
135
|
{/await}
|
|
125
136
|
</svelte:boundary>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { toPng } from "html-to-image";
|
|
3
|
+
import { jsPDF } from "jspdf";
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
report: any;
|
|
7
|
+
utils: any;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { report, utils }: Props = $props();
|
|
11
|
+
const { Button, Icons } = utils.components;
|
|
12
|
+
|
|
13
|
+
let exporting = $state(false);
|
|
14
|
+
|
|
15
|
+
async function handleExport() {
|
|
16
|
+
const container = document.querySelector<HTMLElement>(".charts-export-container");
|
|
17
|
+
if (!container || !report) return;
|
|
18
|
+
exporting = true;
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const backgroundColor = getComputedStyle(document.documentElement).backgroundColor;
|
|
22
|
+
|
|
23
|
+
const imgData = await toPng(container, {
|
|
24
|
+
pixelRatio: 2,
|
|
25
|
+
backgroundColor,
|
|
26
|
+
style: {
|
|
27
|
+
overflow: "visible",
|
|
28
|
+
height: `${container.scrollHeight}px`,
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const img = new Image();
|
|
33
|
+
img.src = imgData;
|
|
34
|
+
await new Promise((r) => (img.onload = r));
|
|
35
|
+
|
|
36
|
+
const pdf = new jsPDF({ orientation: "landscape", unit: "px" });
|
|
37
|
+
const pageWidth = pdf.internal.pageSize.getWidth();
|
|
38
|
+
const pageHeight = pdf.internal.pageSize.getHeight();
|
|
39
|
+
const ratio = Math.min(pageWidth / img.width, pageHeight / img.height);
|
|
40
|
+
|
|
41
|
+
pdf.addImage(imgData, "PNG", 0, 0, img.width * ratio, img.height * ratio);
|
|
42
|
+
pdf.save(`${report.name ?? "report"}.pdf`);
|
|
43
|
+
} finally {
|
|
44
|
+
exporting = false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<Button
|
|
50
|
+
variant="outline"
|
|
51
|
+
class="h-7 px-3 text-xs font-normal"
|
|
52
|
+
Icon={exporting ? Icons.LoaderCircle : Icons.Upload}
|
|
53
|
+
onclick={handleExport}
|
|
54
|
+
disabled={exporting}
|
|
55
|
+
>
|
|
56
|
+
{exporting ? "Exporting..." : "Export"}
|
|
57
|
+
</Button>
|
|
@@ -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 ExportButtonProps = typeof __propDef.props;
|
|
10
|
+
export type ExportButtonEvents = typeof __propDef.events;
|
|
11
|
+
export type ExportButtonSlots = typeof __propDef.slots;
|
|
12
|
+
export default class ExportButton extends SvelteComponentTyped<ExportButtonProps, ExportButtonEvents, ExportButtonSlots> {
|
|
13
|
+
}
|
|
14
|
+
export {};
|
|
@@ -1,33 +1,15 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type { ExtensionProps } from "@lobb-js/studio";
|
|
3
|
-
import type { DateRange } from "bits-ui";
|
|
4
2
|
import { onMount } from "svelte";
|
|
5
3
|
import GridStackComponent from "../../../gridStack.svelte";
|
|
6
4
|
import Chart from "./chart.svelte";
|
|
5
|
+
import ExportButton from "./exportButton.svelte";
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
reportId: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const props: Props = $props();
|
|
13
|
-
const { intlDate } = props.utils;
|
|
7
|
+
const { reportId, utils, ...props }: { reportId: string; utils: any; [key: string]: any } = $props();
|
|
14
8
|
const {
|
|
15
9
|
SidebarTrigger,
|
|
16
10
|
CreateDetailViewButton,
|
|
17
11
|
Icons,
|
|
18
|
-
|
|
19
|
-
} = props.utils.components;
|
|
20
|
-
|
|
21
|
-
let dateRangeValue: DateRange = $state({
|
|
22
|
-
start: intlDate.today(intlDate.getLocalTimeZone()).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
|
-
});
|
|
12
|
+
} = utils.components;
|
|
31
13
|
|
|
32
14
|
let report: any = $state(null);
|
|
33
15
|
let charts: any[] = $state([]);
|
|
@@ -35,13 +17,13 @@
|
|
|
35
17
|
|
|
36
18
|
async function loadCharts() {
|
|
37
19
|
loading = true;
|
|
38
|
-
const reportsRes = await
|
|
20
|
+
const reportsRes = await utils.lobb.findAll("reports_dashboards", {
|
|
39
21
|
sort: "id",
|
|
40
|
-
filter: { id:
|
|
22
|
+
filter: { id: reportId },
|
|
41
23
|
});
|
|
42
24
|
report = (await reportsRes.json()).data[0];
|
|
43
25
|
|
|
44
|
-
const chartsRes = await
|
|
26
|
+
const chartsRes = await utils.lobb.findAll("reports_charts", {
|
|
45
27
|
filter: { report_id: report.id },
|
|
46
28
|
});
|
|
47
29
|
charts = (await chartsRes.json()).data;
|
|
@@ -50,7 +32,7 @@
|
|
|
50
32
|
|
|
51
33
|
async function handleLayoutChange(changes: { id: string; order: number; w: number; h: number }[]) {
|
|
52
34
|
for (const change of changes) {
|
|
53
|
-
await
|
|
35
|
+
await utils.lobb.updateOne("reports_charts", change.id, {
|
|
54
36
|
sort_order: change.order,
|
|
55
37
|
col_span: change.w,
|
|
56
38
|
row_span: change.h,
|
|
@@ -67,7 +49,7 @@
|
|
|
67
49
|
|
|
68
50
|
<div class="report-layout">
|
|
69
51
|
{#if loading}
|
|
70
|
-
<div class="flex h-full w-full flex-col items-center justify-center gap-4">
|
|
52
|
+
<div class="flex h-full w-full flex-col items-center justify-center gap-4" style="grid-row: 1 / -1">
|
|
71
53
|
<Icons.LoaderCircle class="animate-spin opacity-50" size="50" />
|
|
72
54
|
<div class="flex flex-col items-center justify-center">
|
|
73
55
|
<div class="text-muted-foreground">Loading the dashboard...</div>
|
|
@@ -84,7 +66,7 @@
|
|
|
84
66
|
</div>
|
|
85
67
|
</div>
|
|
86
68
|
<div class="flex gap-2 self-end">
|
|
87
|
-
<
|
|
69
|
+
<ExportButton {report} {utils} />
|
|
88
70
|
<CreateDetailViewButton
|
|
89
71
|
collectionName="reports_charts"
|
|
90
72
|
values={{ report_id: { id: props.reportId, name: report.name } }}
|
|
@@ -98,7 +80,7 @@
|
|
|
98
80
|
</div>
|
|
99
81
|
</div>
|
|
100
82
|
|
|
101
|
-
<div class="overflow-auto p-2">
|
|
83
|
+
<div class="charts-export-container overflow-auto p-2">
|
|
102
84
|
{#if charts.length === 0}
|
|
103
85
|
<div class="flex w-full flex-col items-center justify-center gap-4 p-10 text-muted-foreground">
|
|
104
86
|
<Icons.CircleSlash2 class="opacity-50" size="50" />
|
|
@@ -117,15 +99,13 @@
|
|
|
117
99
|
{...gridItemAttrs(chart)}
|
|
118
100
|
>
|
|
119
101
|
<div class="grid-stack-item-content">
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
/>
|
|
128
|
-
{/key}
|
|
102
|
+
<Chart
|
|
103
|
+
chartRecord={chart}
|
|
104
|
+
onChartDeleted={async () => await loadCharts()}
|
|
105
|
+
onChartEdited={async () => await loadCharts()}
|
|
106
|
+
{utils}
|
|
107
|
+
{...props}
|
|
108
|
+
/>
|
|
129
109
|
</div>
|
|
130
110
|
</div>
|
|
131
111
|
{/each}
|
|
@@ -102,4 +102,16 @@
|
|
|
102
102
|
background: transparent;
|
|
103
103
|
padding: 0.5rem;
|
|
104
104
|
}
|
|
105
|
+
|
|
106
|
+
:global(.grid-stack-placeholder > .placeholder-content) {
|
|
107
|
+
position: absolute !important;
|
|
108
|
+
width: auto !important;
|
|
109
|
+
border: 2px dashed color-mix(in oklab, var(--muted-foreground) 25%, transparent) !important;
|
|
110
|
+
border-radius: 0.375rem !important;
|
|
111
|
+
background-color: rgba(0, 0, 0, 0.06) !important;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
:global(.dark .grid-stack-placeholder > .placeholder-content) {
|
|
115
|
+
background-color: rgba(255, 255, 255, 0.06) !important;
|
|
116
|
+
}
|
|
105
117
|
</style>
|
|
@@ -8,20 +8,27 @@
|
|
|
8
8
|
chartRecord: any;
|
|
9
9
|
onChartDeleted?: () => Promise<void>;
|
|
10
10
|
onChartEdited?: () => Promise<void>;
|
|
11
|
-
context?: Record<string, any>;
|
|
12
11
|
}
|
|
13
12
|
|
|
14
13
|
const {
|
|
15
14
|
chartRecord,
|
|
16
15
|
onChartDeleted,
|
|
17
16
|
onChartEdited,
|
|
18
|
-
context,
|
|
19
17
|
...props
|
|
20
18
|
}: Props = $props();
|
|
21
19
|
|
|
22
20
|
const utils = props.utils;
|
|
23
21
|
const { UpdateDetailViewButton, Button } = utils.components;
|
|
24
22
|
const icons = utils.components.Icons;
|
|
23
|
+
|
|
24
|
+
function findCustomChartComponent(type: string): any {
|
|
25
|
+
const extensions = utils.ctx?.extensions ?? {};
|
|
26
|
+
for (const ext of Object.values(extensions) as any[]) {
|
|
27
|
+
const comp = ext.components?.[`reports.chart.${type}`];
|
|
28
|
+
if (comp) return comp;
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
25
32
|
let dataPromise = $state(getChartData());
|
|
26
33
|
|
|
27
34
|
async function getChartData() {
|
|
@@ -30,7 +37,6 @@
|
|
|
30
37
|
chartRecord.id,
|
|
31
38
|
{
|
|
32
39
|
action: "run_query",
|
|
33
|
-
context: JSON.stringify(context),
|
|
34
40
|
},
|
|
35
41
|
);
|
|
36
42
|
|
|
@@ -117,9 +123,14 @@
|
|
|
117
123
|
{:else if data.type === "metric"}
|
|
118
124
|
<Metric input={data.input} />
|
|
119
125
|
{:else}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
126
|
+
{@const CustomChart = findCustomChartComponent(data.type)}
|
|
127
|
+
{#if CustomChart}
|
|
128
|
+
<CustomChart input={data.input} {...props} />
|
|
129
|
+
{:else}
|
|
130
|
+
<div class="text-muted-foreground">
|
|
131
|
+
The ({data.type}) chart type is unsupported
|
|
132
|
+
</div>
|
|
133
|
+
{/if}
|
|
123
134
|
{/if}
|
|
124
135
|
{/await}
|
|
125
136
|
</svelte:boundary>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { toPng } from "html-to-image";
|
|
3
|
+
import { jsPDF } from "jspdf";
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
report: any;
|
|
7
|
+
utils: any;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { report, utils }: Props = $props();
|
|
11
|
+
const { Button, Icons } = utils.components;
|
|
12
|
+
|
|
13
|
+
let exporting = $state(false);
|
|
14
|
+
|
|
15
|
+
async function handleExport() {
|
|
16
|
+
const container = document.querySelector<HTMLElement>(".charts-export-container");
|
|
17
|
+
if (!container || !report) return;
|
|
18
|
+
exporting = true;
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const backgroundColor = getComputedStyle(document.documentElement).backgroundColor;
|
|
22
|
+
|
|
23
|
+
const imgData = await toPng(container, {
|
|
24
|
+
pixelRatio: 2,
|
|
25
|
+
backgroundColor,
|
|
26
|
+
style: {
|
|
27
|
+
overflow: "visible",
|
|
28
|
+
height: `${container.scrollHeight}px`,
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const img = new Image();
|
|
33
|
+
img.src = imgData;
|
|
34
|
+
await new Promise((r) => (img.onload = r));
|
|
35
|
+
|
|
36
|
+
const pdf = new jsPDF({ orientation: "landscape", unit: "px" });
|
|
37
|
+
const pageWidth = pdf.internal.pageSize.getWidth();
|
|
38
|
+
const pageHeight = pdf.internal.pageSize.getHeight();
|
|
39
|
+
const ratio = Math.min(pageWidth / img.width, pageHeight / img.height);
|
|
40
|
+
|
|
41
|
+
pdf.addImage(imgData, "PNG", 0, 0, img.width * ratio, img.height * ratio);
|
|
42
|
+
pdf.save(`${report.name ?? "report"}.pdf`);
|
|
43
|
+
} finally {
|
|
44
|
+
exporting = false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<Button
|
|
50
|
+
variant="outline"
|
|
51
|
+
class="h-7 px-3 text-xs font-normal"
|
|
52
|
+
Icon={exporting ? Icons.LoaderCircle : Icons.Upload}
|
|
53
|
+
onclick={handleExport}
|
|
54
|
+
disabled={exporting}
|
|
55
|
+
>
|
|
56
|
+
{exporting ? "Exporting..." : "Export"}
|
|
57
|
+
</Button>
|
|
@@ -1,33 +1,15 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type { ExtensionProps } from "@lobb-js/studio";
|
|
3
|
-
import type { DateRange } from "bits-ui";
|
|
4
2
|
import { onMount } from "svelte";
|
|
5
3
|
import GridStackComponent from "../../../gridStack.svelte";
|
|
6
4
|
import Chart from "./chart.svelte";
|
|
5
|
+
import ExportButton from "./exportButton.svelte";
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
reportId: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const props: Props = $props();
|
|
13
|
-
const { intlDate } = props.utils;
|
|
7
|
+
const { reportId, utils, ...props }: { reportId: string; utils: any; [key: string]: any } = $props();
|
|
14
8
|
const {
|
|
15
9
|
SidebarTrigger,
|
|
16
10
|
CreateDetailViewButton,
|
|
17
11
|
Icons,
|
|
18
|
-
|
|
19
|
-
} = props.utils.components;
|
|
20
|
-
|
|
21
|
-
let dateRangeValue: DateRange = $state({
|
|
22
|
-
start: intlDate.today(intlDate.getLocalTimeZone()).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
|
-
});
|
|
12
|
+
} = utils.components;
|
|
31
13
|
|
|
32
14
|
let report: any = $state(null);
|
|
33
15
|
let charts: any[] = $state([]);
|
|
@@ -35,13 +17,13 @@
|
|
|
35
17
|
|
|
36
18
|
async function loadCharts() {
|
|
37
19
|
loading = true;
|
|
38
|
-
const reportsRes = await
|
|
20
|
+
const reportsRes = await utils.lobb.findAll("reports_dashboards", {
|
|
39
21
|
sort: "id",
|
|
40
|
-
filter: { id:
|
|
22
|
+
filter: { id: reportId },
|
|
41
23
|
});
|
|
42
24
|
report = (await reportsRes.json()).data[0];
|
|
43
25
|
|
|
44
|
-
const chartsRes = await
|
|
26
|
+
const chartsRes = await utils.lobb.findAll("reports_charts", {
|
|
45
27
|
filter: { report_id: report.id },
|
|
46
28
|
});
|
|
47
29
|
charts = (await chartsRes.json()).data;
|
|
@@ -50,7 +32,7 @@
|
|
|
50
32
|
|
|
51
33
|
async function handleLayoutChange(changes: { id: string; order: number; w: number; h: number }[]) {
|
|
52
34
|
for (const change of changes) {
|
|
53
|
-
await
|
|
35
|
+
await utils.lobb.updateOne("reports_charts", change.id, {
|
|
54
36
|
sort_order: change.order,
|
|
55
37
|
col_span: change.w,
|
|
56
38
|
row_span: change.h,
|
|
@@ -67,7 +49,7 @@
|
|
|
67
49
|
|
|
68
50
|
<div class="report-layout">
|
|
69
51
|
{#if loading}
|
|
70
|
-
<div class="flex h-full w-full flex-col items-center justify-center gap-4">
|
|
52
|
+
<div class="flex h-full w-full flex-col items-center justify-center gap-4" style="grid-row: 1 / -1">
|
|
71
53
|
<Icons.LoaderCircle class="animate-spin opacity-50" size="50" />
|
|
72
54
|
<div class="flex flex-col items-center justify-center">
|
|
73
55
|
<div class="text-muted-foreground">Loading the dashboard...</div>
|
|
@@ -84,7 +66,7 @@
|
|
|
84
66
|
</div>
|
|
85
67
|
</div>
|
|
86
68
|
<div class="flex gap-2 self-end">
|
|
87
|
-
<
|
|
69
|
+
<ExportButton {report} {utils} />
|
|
88
70
|
<CreateDetailViewButton
|
|
89
71
|
collectionName="reports_charts"
|
|
90
72
|
values={{ report_id: { id: props.reportId, name: report.name } }}
|
|
@@ -98,7 +80,7 @@
|
|
|
98
80
|
</div>
|
|
99
81
|
</div>
|
|
100
82
|
|
|
101
|
-
<div class="overflow-auto p-2">
|
|
83
|
+
<div class="charts-export-container overflow-auto p-2">
|
|
102
84
|
{#if charts.length === 0}
|
|
103
85
|
<div class="flex w-full flex-col items-center justify-center gap-4 p-10 text-muted-foreground">
|
|
104
86
|
<Icons.CircleSlash2 class="opacity-50" size="50" />
|
|
@@ -117,15 +99,13 @@
|
|
|
117
99
|
{...gridItemAttrs(chart)}
|
|
118
100
|
>
|
|
119
101
|
<div class="grid-stack-item-content">
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
/>
|
|
128
|
-
{/key}
|
|
102
|
+
<Chart
|
|
103
|
+
chartRecord={chart}
|
|
104
|
+
onChartDeleted={async () => await loadCharts()}
|
|
105
|
+
onChartEdited={async () => await loadCharts()}
|
|
106
|
+
{utils}
|
|
107
|
+
{...props}
|
|
108
|
+
/>
|
|
129
109
|
</div>
|
|
130
110
|
</div>
|
|
131
111
|
{/each}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobb-js/lobb-ext-reports",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -38,13 +38,15 @@
|
|
|
38
38
|
"chart.js": "^4.4.8",
|
|
39
39
|
"gridstack": "^12.6.0",
|
|
40
40
|
"hono": "^4.7.0",
|
|
41
|
+
"html-to-image": "^1.11.13",
|
|
42
|
+
"jspdf": "^4.2.1",
|
|
41
43
|
"lodash-es": "^4.17.21",
|
|
42
44
|
"openapi-types": "^12.1.3"
|
|
43
45
|
},
|
|
44
46
|
"devDependencies": {
|
|
45
47
|
"@faker-js/faker": "^9.6.0",
|
|
46
48
|
"@playwright/test": "^1.58.2",
|
|
47
|
-
"@lobb-js/studio": "^0.
|
|
49
|
+
"@lobb-js/studio": "^0.19.0",
|
|
48
50
|
"@lucide/svelte": "^0.563.1",
|
|
49
51
|
"@sveltejs/adapter-node": "^5.5.4",
|
|
50
52
|
"@sveltejs/kit": "^2.55.0",
|