@lobb-js/lobb-ext-reports 0.4.0 → 0.5.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 +105 -0
- package/dist/lib/components/gridStack.svelte.d.ts +15 -0
- package/dist/lib/components/pages/reports/components/chart.svelte +6 -3
- package/dist/lib/components/pages/reports/components/charts/metric.svelte +23 -0
- package/dist/lib/components/pages/reports/components/charts/metric.svelte.d.ts +14 -0
- package/dist/lib/components/pages/reports/components/report.svelte +82 -92
- package/dist/lib/components/pages/reports/index.svelte +2 -2
- package/extensions/reports/collections/charts.ts +30 -13
- package/extensions/reports/index.ts +0 -2
- package/extensions/reports/studio/lib/components/gridStack.svelte +105 -0
- package/extensions/reports/studio/lib/components/pages/reports/components/chart.svelte +6 -3
- package/extensions/reports/studio/lib/components/pages/reports/components/charts/metric.svelte +23 -0
- package/extensions/reports/studio/lib/components/pages/reports/components/report.svelte +82 -92
- package/extensions/reports/studio/lib/components/pages/reports/index.svelte +2 -2
- package/extensions/reports/tests/configs/simple.ts +3 -9
- package/package.json +12 -11
- package/extensions/reports/studioExtension.json +0 -1
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount, onDestroy, type Snippet } from "svelte";
|
|
3
|
+
import { GridStack } from "gridstack";
|
|
4
|
+
import "gridstack/dist/gridstack.min.css";
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
column?: number;
|
|
8
|
+
cellHeight?: string;
|
|
9
|
+
onLayoutChange?: (changes: { id: string; order: number; w: number; h: number }[]) => void;
|
|
10
|
+
children: Snippet;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
column = 12,
|
|
15
|
+
cellHeight = "150px",
|
|
16
|
+
onLayoutChange,
|
|
17
|
+
children,
|
|
18
|
+
}: Props = $props();
|
|
19
|
+
|
|
20
|
+
let gridEl: HTMLElement | undefined = $state();
|
|
21
|
+
let grid: GridStack | null = null;
|
|
22
|
+
let initialized = false;
|
|
23
|
+
let resizeObserver: ResizeObserver | null = null;
|
|
24
|
+
|
|
25
|
+
onMount(() => {
|
|
26
|
+
if (!gridEl) return;
|
|
27
|
+
|
|
28
|
+
grid = GridStack.init(
|
|
29
|
+
{
|
|
30
|
+
column,
|
|
31
|
+
cellHeight,
|
|
32
|
+
margin: "0.5rem",
|
|
33
|
+
float: false,
|
|
34
|
+
animate: true,
|
|
35
|
+
columnOpts: {
|
|
36
|
+
layout: "moveScale",
|
|
37
|
+
breakpointForWindow: false,
|
|
38
|
+
breakpoints: [
|
|
39
|
+
{ w: 480, c: 1 },
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
gridEl,
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
setTimeout(() => { initialized = true; }, 0);
|
|
47
|
+
|
|
48
|
+
grid.on("dragstop resizestop", () => {
|
|
49
|
+
grid?.compact();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
grid.on("change", () => {
|
|
53
|
+
if (!onLayoutChange || !initialized) return;
|
|
54
|
+
const currentCols = grid!.getColumn();
|
|
55
|
+
if (currentCols === 1) return;
|
|
56
|
+
const sorted = (grid!.engine.nodes as any[])
|
|
57
|
+
.filter((n) => {
|
|
58
|
+
const id = n.el?.getAttribute("data-gs-id");
|
|
59
|
+
return id != null && id !== "null";
|
|
60
|
+
})
|
|
61
|
+
.sort((a, b) => (a.y !== b.y ? a.y - b.y : a.x - b.x))
|
|
62
|
+
.map((node, index) => ({
|
|
63
|
+
id: node.el!.getAttribute("data-gs-id") as string,
|
|
64
|
+
order: index,
|
|
65
|
+
w: Math.round(node.w * (column / currentCols)),
|
|
66
|
+
h: node.h,
|
|
67
|
+
}));
|
|
68
|
+
if (sorted.length > 0) onLayoutChange(sorted);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
resizeObserver = new ResizeObserver((entries) => {
|
|
72
|
+
window.dispatchEvent(new Event("resize"));
|
|
73
|
+
const width = entries[0]?.contentRect.width ?? gridEl!.offsetWidth;
|
|
74
|
+
if (width <= 480) {
|
|
75
|
+
grid?.disable();
|
|
76
|
+
} else {
|
|
77
|
+
grid?.enable();
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
resizeObserver.observe(gridEl);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
onDestroy(() => {
|
|
84
|
+
resizeObserver?.disconnect();
|
|
85
|
+
resizeObserver = null;
|
|
86
|
+
grid?.destroy(false);
|
|
87
|
+
grid = null;
|
|
88
|
+
});
|
|
89
|
+
</script>
|
|
90
|
+
|
|
91
|
+
<div class="grid-stack" bind:this={gridEl}>
|
|
92
|
+
{@render children()}
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
<style>
|
|
96
|
+
:global(.grid-stack-item-content) {
|
|
97
|
+
inset: 0;
|
|
98
|
+
position: absolute;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
:global(.grid-stack) {
|
|
102
|
+
background: transparent;
|
|
103
|
+
padding: 0.5rem;
|
|
104
|
+
}
|
|
105
|
+
</style>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { SvelteComponentTyped } from "svelte";
|
|
2
|
+
import "gridstack/dist/gridstack.min.css";
|
|
3
|
+
declare const __propDef: {
|
|
4
|
+
props: Record<string, never>;
|
|
5
|
+
events: {
|
|
6
|
+
[evt: string]: CustomEvent<any>;
|
|
7
|
+
};
|
|
8
|
+
slots: {};
|
|
9
|
+
};
|
|
10
|
+
export type GridStackProps = typeof __propDef.props;
|
|
11
|
+
export type GridStackEvents = typeof __propDef.events;
|
|
12
|
+
export type GridStackSlots = typeof __propDef.slots;
|
|
13
|
+
export default class GridStack extends SvelteComponentTyped<GridStackProps, GridStackEvents, GridStackSlots> {
|
|
14
|
+
}
|
|
15
|
+
export {};
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import type { ExtensionProps } from "@lobb-js/studio";
|
|
3
3
|
import Table from "./charts/table.svelte";
|
|
4
4
|
import ChartJs from "./charts/chartJs.svelte";
|
|
5
|
+
import Metric from "./charts/metric.svelte";
|
|
5
6
|
|
|
6
7
|
interface Props extends ExtensionProps {
|
|
7
8
|
chartRecord: any;
|
|
@@ -57,7 +58,7 @@
|
|
|
57
58
|
}
|
|
58
59
|
</script>
|
|
59
60
|
|
|
60
|
-
<div class="gridContainer h-
|
|
61
|
+
<div class="gridContainer h-full rounded-md border bg-background">
|
|
61
62
|
<div class="flex items-center justify-between border-b p-2 text-sm">
|
|
62
63
|
<div>{chartRecord.title}</div>
|
|
63
64
|
<div class="flex">
|
|
@@ -78,7 +79,7 @@
|
|
|
78
79
|
></Button>
|
|
79
80
|
</div>
|
|
80
81
|
</div>
|
|
81
|
-
<div class="flex items-center justify-center overflow-
|
|
82
|
+
<div class="flex min-h-0 items-center justify-center overflow-hidden">
|
|
82
83
|
<svelte:boundary>
|
|
83
84
|
{#snippet failed(error, reset)}
|
|
84
85
|
<div
|
|
@@ -93,7 +94,7 @@
|
|
|
93
94
|
{/snippet}
|
|
94
95
|
{#await dataPromise}
|
|
95
96
|
<div
|
|
96
|
-
class="flex flex-col gap-4 h-full w-full
|
|
97
|
+
class="flex flex-col gap-4 h-full w-full justify-center items-center"
|
|
97
98
|
>
|
|
98
99
|
<icons.LoaderCircle
|
|
99
100
|
class="animate-spin opacity-50"
|
|
@@ -113,6 +114,8 @@
|
|
|
113
114
|
<Table input={data.input} {...props} />
|
|
114
115
|
{:else if data.type === "chartjs"}
|
|
115
116
|
<ChartJs input={data.input} {...props} />
|
|
117
|
+
{:else if data.type === "metric"}
|
|
118
|
+
<Metric input={data.input} />
|
|
116
119
|
{:else}
|
|
117
120
|
<div class="text-muted-foreground">
|
|
118
121
|
The ({data.type}) chart type is unsupported
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Input {
|
|
3
|
+
value: string | number;
|
|
4
|
+
label?: string;
|
|
5
|
+
prefix?: string;
|
|
6
|
+
suffix?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
input: Input;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { input }: Props = $props();
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<div class="flex h-full w-full select-none flex-col items-center justify-center gap-1 p-4">
|
|
17
|
+
<div class="text-5xl font-bold tabular-nums tracking-tight">
|
|
18
|
+
{#if input.prefix}<span class="text-2xl font-medium text-muted-foreground">{input.prefix}</span>{/if}{input.value}{#if input.suffix}<span class="text-2xl font-medium text-muted-foreground">{input.suffix}</span>{/if}
|
|
19
|
+
</div>
|
|
20
|
+
{#if input.label}
|
|
21
|
+
<div class="text-sm text-muted-foreground">{input.label}</div>
|
|
22
|
+
{/if}
|
|
23
|
+
</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 MetricProps = typeof __propDef.props;
|
|
10
|
+
export type MetricEvents = typeof __propDef.events;
|
|
11
|
+
export type MetricSlots = typeof __propDef.slots;
|
|
12
|
+
export default class Metric extends SvelteComponentTyped<MetricProps, MetricEvents, MetricSlots> {
|
|
13
|
+
}
|
|
14
|
+
export {};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { ExtensionProps } from "@lobb-js/studio";
|
|
3
3
|
import type { DateRange } from "bits-ui";
|
|
4
|
+
import { onMount } from "svelte";
|
|
5
|
+
import GridStackComponent from "../../../gridStack.svelte";
|
|
4
6
|
import Chart from "./chart.svelte";
|
|
5
7
|
|
|
6
8
|
interface Props extends ExtensionProps {
|
|
@@ -13,13 +15,11 @@
|
|
|
13
15
|
SidebarTrigger,
|
|
14
16
|
CreateDetailViewButton,
|
|
15
17
|
Icons,
|
|
16
|
-
RangeCalendarButton
|
|
18
|
+
RangeCalendarButton
|
|
17
19
|
} = props.utils.components;
|
|
18
20
|
|
|
19
21
|
let dateRangeValue: DateRange = $state({
|
|
20
|
-
start: intlDate
|
|
21
|
-
.today(intlDate.getLocalTimeZone())
|
|
22
|
-
.subtract({ days: 30 }),
|
|
22
|
+
start: intlDate.today(intlDate.getLocalTimeZone()).subtract({ days: 30 }),
|
|
23
23
|
end: intlDate.today(intlDate.getLocalTimeZone()),
|
|
24
24
|
});
|
|
25
25
|
let context = $derived({
|
|
@@ -30,127 +30,117 @@
|
|
|
30
30
|
});
|
|
31
31
|
|
|
32
32
|
let report: any = $state(null);
|
|
33
|
-
let
|
|
33
|
+
let charts: any[] = $state([]);
|
|
34
|
+
let loading = $state(true);
|
|
34
35
|
|
|
35
|
-
async function
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
36
|
+
async function loadCharts() {
|
|
37
|
+
loading = true;
|
|
38
|
+
const reportsRes = await props.utils.lobb.findAll("reports_dashboards", {
|
|
39
|
+
sort: "id",
|
|
40
|
+
filter: { id: props.reportId },
|
|
41
|
+
});
|
|
42
|
+
report = (await reportsRes.json()).data[0];
|
|
43
|
+
|
|
44
|
+
const chartsRes = await props.utils.lobb.findAll("reports_charts", {
|
|
45
|
+
filter: { report_id: report.id },
|
|
46
|
+
});
|
|
47
|
+
charts = (await chartsRes.json()).data;
|
|
48
|
+
loading = false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function handleLayoutChange(changes: { id: string; order: number; w: number; h: number }[]) {
|
|
52
|
+
for (const change of changes) {
|
|
53
|
+
await props.utils.lobb.updateOne("reports_charts", change.id, {
|
|
54
|
+
sort_order: change.order,
|
|
55
|
+
col_span: change.w,
|
|
56
|
+
row_span: change.h,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
55
59
|
}
|
|
56
60
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
chartsPromise = Promise.resolve(await getCharts());
|
|
61
|
+
function gridItemAttrs(chart: any): Record<string, unknown> {
|
|
62
|
+
return { "gs-w": chart.col_span ?? 6, "gs-h": chart.row_span ?? 2, "gs-auto-position": "true" };
|
|
60
63
|
}
|
|
64
|
+
|
|
65
|
+
onMount(() => loadCharts());
|
|
61
66
|
</script>
|
|
62
67
|
|
|
63
|
-
<div class="
|
|
64
|
-
{#
|
|
65
|
-
<div
|
|
66
|
-
class="flex flex-col gap-4 h-full w-full absolute justify-center items-center"
|
|
67
|
-
>
|
|
68
|
+
<div class="report-layout">
|
|
69
|
+
{#if loading}
|
|
70
|
+
<div class="flex h-full w-full flex-col items-center justify-center gap-4">
|
|
68
71
|
<Icons.LoaderCircle class="animate-spin opacity-50" size="50" />
|
|
69
72
|
<div class="flex flex-col items-center justify-center">
|
|
70
|
-
<div class="text-muted-foreground">
|
|
71
|
-
|
|
72
|
-
</div>
|
|
73
|
-
<div class="text-muted-foreground text-xs">
|
|
74
|
-
Loading all the charts of the selected dashboard
|
|
75
|
-
</div>
|
|
73
|
+
<div class="text-muted-foreground">Loading the dashboard...</div>
|
|
74
|
+
<div class="text-xs text-muted-foreground">Loading all the charts of the selected dashboard</div>
|
|
76
75
|
</div>
|
|
77
76
|
</div>
|
|
78
|
-
{:
|
|
79
|
-
<div
|
|
80
|
-
class="flex justify-between gap-2 overflow-auto border-b bg-background p-2"
|
|
81
|
-
>
|
|
77
|
+
{:else}
|
|
78
|
+
<div class="flex shrink-0 items-center justify-between gap-2 border-b bg-background p-2">
|
|
82
79
|
<div class="flex gap-2">
|
|
83
|
-
<div class="mt-1">
|
|
84
|
-
<SidebarTrigger />
|
|
85
|
-
</div>
|
|
80
|
+
<div class="mt-1"><SidebarTrigger /></div>
|
|
86
81
|
<div class="flex flex-col justify-center">
|
|
87
82
|
<h2 class="font-medium text-primary">{report.name}</h2>
|
|
88
|
-
<div class="text-xs text-muted-foreground">
|
|
89
|
-
{report.description}
|
|
90
|
-
</div>
|
|
83
|
+
<div class="text-xs text-muted-foreground">{report.description}</div>
|
|
91
84
|
</div>
|
|
92
85
|
</div>
|
|
93
86
|
<div class="flex gap-2 self-end">
|
|
94
87
|
<RangeCalendarButton bind:value={dateRangeValue} />
|
|
95
88
|
<CreateDetailViewButton
|
|
96
89
|
collectionName="reports_charts"
|
|
97
|
-
values={{
|
|
98
|
-
report_id: {
|
|
99
|
-
id: props.reportId,
|
|
100
|
-
name: report.name,
|
|
101
|
-
},
|
|
102
|
-
}}
|
|
90
|
+
values={{ report_id: { id: props.reportId, name: report.name } }}
|
|
103
91
|
variant="default"
|
|
104
92
|
class="h-7 px-3 text-xs font-normal"
|
|
105
93
|
Icon={Icons.Plus}
|
|
106
|
-
onSuccessfullSave={async () => await
|
|
94
|
+
onSuccessfullSave={async () => await loadCharts()}
|
|
107
95
|
>
|
|
108
96
|
Create chart
|
|
109
97
|
</CreateDetailViewButton>
|
|
110
98
|
</div>
|
|
111
99
|
</div>
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
>
|
|
118
|
-
<
|
|
119
|
-
<div class="
|
|
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>
|
|
100
|
+
|
|
101
|
+
<div class="overflow-auto p-2">
|
|
102
|
+
{#if charts.length === 0}
|
|
103
|
+
<div class="flex w-full flex-col items-center justify-center gap-4 p-10 text-muted-foreground">
|
|
104
|
+
<Icons.CircleSlash2 class="opacity-50" size="50" />
|
|
105
|
+
<div class="flex flex-col items-center justify-center">
|
|
106
|
+
<div>No charts available</div>
|
|
107
|
+
<div class="text-xs">Create a new chart to fill this report page</div>
|
|
125
108
|
</div>
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
{
|
|
134
|
-
{...
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
109
|
+
</div>
|
|
110
|
+
{:else}
|
|
111
|
+
{#key charts}
|
|
112
|
+
<GridStackComponent onLayoutChange={handleLayoutChange}>
|
|
113
|
+
{#each [...charts].sort((a, b) => (a.sort_order ?? 0) - (b.sort_order ?? 0)) as chart (chart.id)}
|
|
114
|
+
<div
|
|
115
|
+
class="grid-stack-item"
|
|
116
|
+
data-gs-id={chart.id}
|
|
117
|
+
{...gridItemAttrs(chart)}
|
|
118
|
+
>
|
|
119
|
+
<div class="grid-stack-item-content">
|
|
120
|
+
{#key context}
|
|
121
|
+
<Chart
|
|
122
|
+
chartRecord={chart}
|
|
123
|
+
onChartDeleted={async () => await loadCharts()}
|
|
124
|
+
onChartEdited={async () => await loadCharts()}
|
|
125
|
+
{context}
|
|
126
|
+
{...props}
|
|
127
|
+
/>
|
|
128
|
+
{/key}
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
{/each}
|
|
132
|
+
</GridStackComponent>
|
|
133
|
+
{/key}
|
|
134
|
+
{/if}
|
|
140
135
|
</div>
|
|
141
|
-
{/
|
|
136
|
+
{/if}
|
|
142
137
|
</div>
|
|
143
138
|
|
|
144
139
|
<style>
|
|
145
|
-
.
|
|
140
|
+
.report-layout {
|
|
146
141
|
display: grid;
|
|
147
|
-
grid-template-columns: 1fr;
|
|
148
142
|
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;
|
|
143
|
+
width: 100%;
|
|
144
|
+
height: 100%;
|
|
155
145
|
}
|
|
156
146
|
</style>
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
|
|
64
64
|
<Sidebar title="Reports" data={sideBarData}>
|
|
65
65
|
{#snippet belowSearch()}
|
|
66
|
-
<div class="p-2
|
|
66
|
+
<div class="p-2">
|
|
67
67
|
<CreateDetailViewButton
|
|
68
68
|
collectionName="reports_dashboards"
|
|
69
69
|
variant="outline"
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
</div>
|
|
106
106
|
{/if}
|
|
107
107
|
{/snippet}
|
|
108
|
-
<div class="
|
|
108
|
+
<div class="flex h-full w-full flex-col bg-muted">
|
|
109
109
|
{#if reportId}
|
|
110
110
|
{#key reportId}
|
|
111
111
|
<Report {utils} {reportId} {...props} />
|
|
@@ -8,30 +8,47 @@ export const charts: CollectionConfig = {
|
|
|
8
8
|
},
|
|
9
9
|
"report_id": {
|
|
10
10
|
"type": "integer",
|
|
11
|
-
"
|
|
12
|
-
"required": true,
|
|
13
|
-
},
|
|
11
|
+
"required": true,
|
|
14
12
|
},
|
|
15
13
|
"title": {
|
|
16
14
|
"type": "string",
|
|
17
15
|
"length": 255,
|
|
18
|
-
"
|
|
19
|
-
"required": true,
|
|
20
|
-
},
|
|
16
|
+
"required": true,
|
|
21
17
|
},
|
|
22
18
|
"description": {
|
|
23
19
|
"type": "string",
|
|
24
20
|
"length": 255,
|
|
25
21
|
},
|
|
22
|
+
"sort_order": {
|
|
23
|
+
"type": "integer",
|
|
24
|
+
},
|
|
25
|
+
"row_span": {
|
|
26
|
+
"type": "integer",
|
|
27
|
+
"default": 2,
|
|
28
|
+
},
|
|
29
|
+
"col_span": {
|
|
30
|
+
"type": "integer",
|
|
31
|
+
"default": 6,
|
|
32
|
+
"enum": [
|
|
33
|
+
{ "value": 1, "description": "1/12" },
|
|
34
|
+
{ "value": 2, "description": "2/12" },
|
|
35
|
+
{ "value": 3, "description": "3/12" },
|
|
36
|
+
{ "value": 4, "description": "4/12" },
|
|
37
|
+
{ "value": 5, "description": "5/12" },
|
|
38
|
+
{ "value": 6, "description": "6/12" },
|
|
39
|
+
{ "value": 7, "description": "7/12" },
|
|
40
|
+
{ "value": 8, "description": "8/12" },
|
|
41
|
+
{ "value": 9, "description": "9/12" },
|
|
42
|
+
{ "value": 10, "description": "10/12" },
|
|
43
|
+
{ "value": 11, "description": "11/12" },
|
|
44
|
+
{ "value": 12, "description": "Full width" },
|
|
45
|
+
],
|
|
46
|
+
},
|
|
26
47
|
"chart": {
|
|
27
48
|
"type": "text",
|
|
28
|
-
"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"pre_processors": {
|
|
32
|
-
"default":
|
|
33
|
-
"async function chart(query: QueryFn, context: Record<string, unknown>) {\n\treturn {\n\t\ttype: 'table',\n\t\tinput: {\n\t\t\tdata: []\n\t\t}\n\t};\n}",
|
|
34
|
-
},
|
|
49
|
+
"required": true,
|
|
50
|
+
"default":
|
|
51
|
+
"async function chart(query: QueryFn, context: Record<string, unknown>) {\n\treturn {\n\t\ttype: 'table',\n\t\tinput: {\n\t\t\tdata: []\n\t\t}\n\t};\n}",
|
|
35
52
|
"ui": {
|
|
36
53
|
"input": {
|
|
37
54
|
"type": "code",
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { Extension } from "@lobb-js/core";
|
|
2
2
|
|
|
3
3
|
import packageJson from "../../package.json" with { type: "json" };
|
|
4
|
-
import studioExtension from "./studioExtension.json" with { type: "json" };
|
|
5
4
|
import { collections } from "./collections/index.ts";
|
|
6
5
|
import { migrations } from "./migrations.ts";
|
|
7
6
|
import { meta } from "./meta.ts";
|
|
@@ -19,6 +18,5 @@ export default function extension(): Extension {
|
|
|
19
18
|
migrations: migrations,
|
|
20
19
|
meta: meta,
|
|
21
20
|
openapi: openapi,
|
|
22
|
-
dashboard: studioExtension,
|
|
23
21
|
};
|
|
24
22
|
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount, onDestroy, type Snippet } from "svelte";
|
|
3
|
+
import { GridStack } from "gridstack";
|
|
4
|
+
import "gridstack/dist/gridstack.min.css";
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
column?: number;
|
|
8
|
+
cellHeight?: string;
|
|
9
|
+
onLayoutChange?: (changes: { id: string; order: number; w: number; h: number }[]) => void;
|
|
10
|
+
children: Snippet;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
column = 12,
|
|
15
|
+
cellHeight = "150px",
|
|
16
|
+
onLayoutChange,
|
|
17
|
+
children,
|
|
18
|
+
}: Props = $props();
|
|
19
|
+
|
|
20
|
+
let gridEl: HTMLElement | undefined = $state();
|
|
21
|
+
let grid: GridStack | null = null;
|
|
22
|
+
let initialized = false;
|
|
23
|
+
let resizeObserver: ResizeObserver | null = null;
|
|
24
|
+
|
|
25
|
+
onMount(() => {
|
|
26
|
+
if (!gridEl) return;
|
|
27
|
+
|
|
28
|
+
grid = GridStack.init(
|
|
29
|
+
{
|
|
30
|
+
column,
|
|
31
|
+
cellHeight,
|
|
32
|
+
margin: "0.5rem",
|
|
33
|
+
float: false,
|
|
34
|
+
animate: true,
|
|
35
|
+
columnOpts: {
|
|
36
|
+
layout: "moveScale",
|
|
37
|
+
breakpointForWindow: false,
|
|
38
|
+
breakpoints: [
|
|
39
|
+
{ w: 480, c: 1 },
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
gridEl,
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
setTimeout(() => { initialized = true; }, 0);
|
|
47
|
+
|
|
48
|
+
grid.on("dragstop resizestop", () => {
|
|
49
|
+
grid?.compact();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
grid.on("change", () => {
|
|
53
|
+
if (!onLayoutChange || !initialized) return;
|
|
54
|
+
const currentCols = grid!.getColumn();
|
|
55
|
+
if (currentCols === 1) return;
|
|
56
|
+
const sorted = (grid!.engine.nodes as any[])
|
|
57
|
+
.filter((n) => {
|
|
58
|
+
const id = n.el?.getAttribute("data-gs-id");
|
|
59
|
+
return id != null && id !== "null";
|
|
60
|
+
})
|
|
61
|
+
.sort((a, b) => (a.y !== b.y ? a.y - b.y : a.x - b.x))
|
|
62
|
+
.map((node, index) => ({
|
|
63
|
+
id: node.el!.getAttribute("data-gs-id") as string,
|
|
64
|
+
order: index,
|
|
65
|
+
w: Math.round(node.w * (column / currentCols)),
|
|
66
|
+
h: node.h,
|
|
67
|
+
}));
|
|
68
|
+
if (sorted.length > 0) onLayoutChange(sorted);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
resizeObserver = new ResizeObserver((entries) => {
|
|
72
|
+
window.dispatchEvent(new Event("resize"));
|
|
73
|
+
const width = entries[0]?.contentRect.width ?? gridEl!.offsetWidth;
|
|
74
|
+
if (width <= 480) {
|
|
75
|
+
grid?.disable();
|
|
76
|
+
} else {
|
|
77
|
+
grid?.enable();
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
resizeObserver.observe(gridEl);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
onDestroy(() => {
|
|
84
|
+
resizeObserver?.disconnect();
|
|
85
|
+
resizeObserver = null;
|
|
86
|
+
grid?.destroy(false);
|
|
87
|
+
grid = null;
|
|
88
|
+
});
|
|
89
|
+
</script>
|
|
90
|
+
|
|
91
|
+
<div class="grid-stack" bind:this={gridEl}>
|
|
92
|
+
{@render children()}
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
<style>
|
|
96
|
+
:global(.grid-stack-item-content) {
|
|
97
|
+
inset: 0;
|
|
98
|
+
position: absolute;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
:global(.grid-stack) {
|
|
102
|
+
background: transparent;
|
|
103
|
+
padding: 0.5rem;
|
|
104
|
+
}
|
|
105
|
+
</style>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import type { ExtensionProps } from "@lobb-js/studio";
|
|
3
3
|
import Table from "./charts/table.svelte";
|
|
4
4
|
import ChartJs from "./charts/chartJs.svelte";
|
|
5
|
+
import Metric from "./charts/metric.svelte";
|
|
5
6
|
|
|
6
7
|
interface Props extends ExtensionProps {
|
|
7
8
|
chartRecord: any;
|
|
@@ -57,7 +58,7 @@
|
|
|
57
58
|
}
|
|
58
59
|
</script>
|
|
59
60
|
|
|
60
|
-
<div class="gridContainer h-
|
|
61
|
+
<div class="gridContainer h-full rounded-md border bg-background">
|
|
61
62
|
<div class="flex items-center justify-between border-b p-2 text-sm">
|
|
62
63
|
<div>{chartRecord.title}</div>
|
|
63
64
|
<div class="flex">
|
|
@@ -78,7 +79,7 @@
|
|
|
78
79
|
></Button>
|
|
79
80
|
</div>
|
|
80
81
|
</div>
|
|
81
|
-
<div class="flex items-center justify-center overflow-
|
|
82
|
+
<div class="flex min-h-0 items-center justify-center overflow-hidden">
|
|
82
83
|
<svelte:boundary>
|
|
83
84
|
{#snippet failed(error, reset)}
|
|
84
85
|
<div
|
|
@@ -93,7 +94,7 @@
|
|
|
93
94
|
{/snippet}
|
|
94
95
|
{#await dataPromise}
|
|
95
96
|
<div
|
|
96
|
-
class="flex flex-col gap-4 h-full w-full
|
|
97
|
+
class="flex flex-col gap-4 h-full w-full justify-center items-center"
|
|
97
98
|
>
|
|
98
99
|
<icons.LoaderCircle
|
|
99
100
|
class="animate-spin opacity-50"
|
|
@@ -113,6 +114,8 @@
|
|
|
113
114
|
<Table input={data.input} {...props} />
|
|
114
115
|
{:else if data.type === "chartjs"}
|
|
115
116
|
<ChartJs input={data.input} {...props} />
|
|
117
|
+
{:else if data.type === "metric"}
|
|
118
|
+
<Metric input={data.input} />
|
|
116
119
|
{:else}
|
|
117
120
|
<div class="text-muted-foreground">
|
|
118
121
|
The ({data.type}) chart type is unsupported
|
package/extensions/reports/studio/lib/components/pages/reports/components/charts/metric.svelte
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Input {
|
|
3
|
+
value: string | number;
|
|
4
|
+
label?: string;
|
|
5
|
+
prefix?: string;
|
|
6
|
+
suffix?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
input: Input;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { input }: Props = $props();
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<div class="flex h-full w-full select-none flex-col items-center justify-center gap-1 p-4">
|
|
17
|
+
<div class="text-5xl font-bold tabular-nums tracking-tight">
|
|
18
|
+
{#if input.prefix}<span class="text-2xl font-medium text-muted-foreground">{input.prefix}</span>{/if}{input.value}{#if input.suffix}<span class="text-2xl font-medium text-muted-foreground">{input.suffix}</span>{/if}
|
|
19
|
+
</div>
|
|
20
|
+
{#if input.label}
|
|
21
|
+
<div class="text-sm text-muted-foreground">{input.label}</div>
|
|
22
|
+
{/if}
|
|
23
|
+
</div>
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { ExtensionProps } from "@lobb-js/studio";
|
|
3
3
|
import type { DateRange } from "bits-ui";
|
|
4
|
+
import { onMount } from "svelte";
|
|
5
|
+
import GridStackComponent from "../../../gridStack.svelte";
|
|
4
6
|
import Chart from "./chart.svelte";
|
|
5
7
|
|
|
6
8
|
interface Props extends ExtensionProps {
|
|
@@ -13,13 +15,11 @@
|
|
|
13
15
|
SidebarTrigger,
|
|
14
16
|
CreateDetailViewButton,
|
|
15
17
|
Icons,
|
|
16
|
-
RangeCalendarButton
|
|
18
|
+
RangeCalendarButton
|
|
17
19
|
} = props.utils.components;
|
|
18
20
|
|
|
19
21
|
let dateRangeValue: DateRange = $state({
|
|
20
|
-
start: intlDate
|
|
21
|
-
.today(intlDate.getLocalTimeZone())
|
|
22
|
-
.subtract({ days: 30 }),
|
|
22
|
+
start: intlDate.today(intlDate.getLocalTimeZone()).subtract({ days: 30 }),
|
|
23
23
|
end: intlDate.today(intlDate.getLocalTimeZone()),
|
|
24
24
|
});
|
|
25
25
|
let context = $derived({
|
|
@@ -30,127 +30,117 @@
|
|
|
30
30
|
});
|
|
31
31
|
|
|
32
32
|
let report: any = $state(null);
|
|
33
|
-
let
|
|
33
|
+
let charts: any[] = $state([]);
|
|
34
|
+
let loading = $state(true);
|
|
34
35
|
|
|
35
|
-
async function
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
36
|
+
async function loadCharts() {
|
|
37
|
+
loading = true;
|
|
38
|
+
const reportsRes = await props.utils.lobb.findAll("reports_dashboards", {
|
|
39
|
+
sort: "id",
|
|
40
|
+
filter: { id: props.reportId },
|
|
41
|
+
});
|
|
42
|
+
report = (await reportsRes.json()).data[0];
|
|
43
|
+
|
|
44
|
+
const chartsRes = await props.utils.lobb.findAll("reports_charts", {
|
|
45
|
+
filter: { report_id: report.id },
|
|
46
|
+
});
|
|
47
|
+
charts = (await chartsRes.json()).data;
|
|
48
|
+
loading = false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function handleLayoutChange(changes: { id: string; order: number; w: number; h: number }[]) {
|
|
52
|
+
for (const change of changes) {
|
|
53
|
+
await props.utils.lobb.updateOne("reports_charts", change.id, {
|
|
54
|
+
sort_order: change.order,
|
|
55
|
+
col_span: change.w,
|
|
56
|
+
row_span: change.h,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
55
59
|
}
|
|
56
60
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
chartsPromise = Promise.resolve(await getCharts());
|
|
61
|
+
function gridItemAttrs(chart: any): Record<string, unknown> {
|
|
62
|
+
return { "gs-w": chart.col_span ?? 6, "gs-h": chart.row_span ?? 2, "gs-auto-position": "true" };
|
|
60
63
|
}
|
|
64
|
+
|
|
65
|
+
onMount(() => loadCharts());
|
|
61
66
|
</script>
|
|
62
67
|
|
|
63
|
-
<div class="
|
|
64
|
-
{#
|
|
65
|
-
<div
|
|
66
|
-
class="flex flex-col gap-4 h-full w-full absolute justify-center items-center"
|
|
67
|
-
>
|
|
68
|
+
<div class="report-layout">
|
|
69
|
+
{#if loading}
|
|
70
|
+
<div class="flex h-full w-full flex-col items-center justify-center gap-4">
|
|
68
71
|
<Icons.LoaderCircle class="animate-spin opacity-50" size="50" />
|
|
69
72
|
<div class="flex flex-col items-center justify-center">
|
|
70
|
-
<div class="text-muted-foreground">
|
|
71
|
-
|
|
72
|
-
</div>
|
|
73
|
-
<div class="text-muted-foreground text-xs">
|
|
74
|
-
Loading all the charts of the selected dashboard
|
|
75
|
-
</div>
|
|
73
|
+
<div class="text-muted-foreground">Loading the dashboard...</div>
|
|
74
|
+
<div class="text-xs text-muted-foreground">Loading all the charts of the selected dashboard</div>
|
|
76
75
|
</div>
|
|
77
76
|
</div>
|
|
78
|
-
{:
|
|
79
|
-
<div
|
|
80
|
-
class="flex justify-between gap-2 overflow-auto border-b bg-background p-2"
|
|
81
|
-
>
|
|
77
|
+
{:else}
|
|
78
|
+
<div class="flex shrink-0 items-center justify-between gap-2 border-b bg-background p-2">
|
|
82
79
|
<div class="flex gap-2">
|
|
83
|
-
<div class="mt-1">
|
|
84
|
-
<SidebarTrigger />
|
|
85
|
-
</div>
|
|
80
|
+
<div class="mt-1"><SidebarTrigger /></div>
|
|
86
81
|
<div class="flex flex-col justify-center">
|
|
87
82
|
<h2 class="font-medium text-primary">{report.name}</h2>
|
|
88
|
-
<div class="text-xs text-muted-foreground">
|
|
89
|
-
{report.description}
|
|
90
|
-
</div>
|
|
83
|
+
<div class="text-xs text-muted-foreground">{report.description}</div>
|
|
91
84
|
</div>
|
|
92
85
|
</div>
|
|
93
86
|
<div class="flex gap-2 self-end">
|
|
94
87
|
<RangeCalendarButton bind:value={dateRangeValue} />
|
|
95
88
|
<CreateDetailViewButton
|
|
96
89
|
collectionName="reports_charts"
|
|
97
|
-
values={{
|
|
98
|
-
report_id: {
|
|
99
|
-
id: props.reportId,
|
|
100
|
-
name: report.name,
|
|
101
|
-
},
|
|
102
|
-
}}
|
|
90
|
+
values={{ report_id: { id: props.reportId, name: report.name } }}
|
|
103
91
|
variant="default"
|
|
104
92
|
class="h-7 px-3 text-xs font-normal"
|
|
105
93
|
Icon={Icons.Plus}
|
|
106
|
-
onSuccessfullSave={async () => await
|
|
94
|
+
onSuccessfullSave={async () => await loadCharts()}
|
|
107
95
|
>
|
|
108
96
|
Create chart
|
|
109
97
|
</CreateDetailViewButton>
|
|
110
98
|
</div>
|
|
111
99
|
</div>
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
>
|
|
118
|
-
<
|
|
119
|
-
<div class="
|
|
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>
|
|
100
|
+
|
|
101
|
+
<div class="overflow-auto p-2">
|
|
102
|
+
{#if charts.length === 0}
|
|
103
|
+
<div class="flex w-full flex-col items-center justify-center gap-4 p-10 text-muted-foreground">
|
|
104
|
+
<Icons.CircleSlash2 class="opacity-50" size="50" />
|
|
105
|
+
<div class="flex flex-col items-center justify-center">
|
|
106
|
+
<div>No charts available</div>
|
|
107
|
+
<div class="text-xs">Create a new chart to fill this report page</div>
|
|
125
108
|
</div>
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
{
|
|
134
|
-
{...
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
109
|
+
</div>
|
|
110
|
+
{:else}
|
|
111
|
+
{#key charts}
|
|
112
|
+
<GridStackComponent onLayoutChange={handleLayoutChange}>
|
|
113
|
+
{#each [...charts].sort((a, b) => (a.sort_order ?? 0) - (b.sort_order ?? 0)) as chart (chart.id)}
|
|
114
|
+
<div
|
|
115
|
+
class="grid-stack-item"
|
|
116
|
+
data-gs-id={chart.id}
|
|
117
|
+
{...gridItemAttrs(chart)}
|
|
118
|
+
>
|
|
119
|
+
<div class="grid-stack-item-content">
|
|
120
|
+
{#key context}
|
|
121
|
+
<Chart
|
|
122
|
+
chartRecord={chart}
|
|
123
|
+
onChartDeleted={async () => await loadCharts()}
|
|
124
|
+
onChartEdited={async () => await loadCharts()}
|
|
125
|
+
{context}
|
|
126
|
+
{...props}
|
|
127
|
+
/>
|
|
128
|
+
{/key}
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
{/each}
|
|
132
|
+
</GridStackComponent>
|
|
133
|
+
{/key}
|
|
134
|
+
{/if}
|
|
140
135
|
</div>
|
|
141
|
-
{/
|
|
136
|
+
{/if}
|
|
142
137
|
</div>
|
|
143
138
|
|
|
144
139
|
<style>
|
|
145
|
-
.
|
|
140
|
+
.report-layout {
|
|
146
141
|
display: grid;
|
|
147
|
-
grid-template-columns: 1fr;
|
|
148
142
|
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;
|
|
143
|
+
width: 100%;
|
|
144
|
+
height: 100%;
|
|
155
145
|
}
|
|
156
146
|
</style>
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
|
|
64
64
|
<Sidebar title="Reports" data={sideBarData}>
|
|
65
65
|
{#snippet belowSearch()}
|
|
66
|
-
<div class="p-2
|
|
66
|
+
<div class="p-2">
|
|
67
67
|
<CreateDetailViewButton
|
|
68
68
|
collectionName="reports_dashboards"
|
|
69
69
|
variant="outline"
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
</div>
|
|
106
106
|
{/if}
|
|
107
107
|
{/snippet}
|
|
108
|
-
<div class="
|
|
108
|
+
<div class="flex h-full w-full flex-col bg-muted">
|
|
109
109
|
{#if reportId}
|
|
110
110
|
{#key reportId}
|
|
111
111
|
<Report {utils} {reportId} {...props} />
|
|
@@ -33,21 +33,15 @@ export const simpleConfig: Config = {
|
|
|
33
33
|
donor_name: {
|
|
34
34
|
type: "string",
|
|
35
35
|
length: 255,
|
|
36
|
-
|
|
37
|
-
required: true,
|
|
38
|
-
},
|
|
36
|
+
required: true,
|
|
39
37
|
},
|
|
40
38
|
amount: {
|
|
41
39
|
type: "decimal",
|
|
42
|
-
|
|
43
|
-
required: true,
|
|
44
|
-
},
|
|
40
|
+
required: true,
|
|
45
41
|
},
|
|
46
42
|
created_at: {
|
|
47
43
|
type: "date",
|
|
48
|
-
|
|
49
|
-
default: "{{ now }}",
|
|
50
|
-
},
|
|
44
|
+
default: "{{ now }}",
|
|
51
45
|
},
|
|
52
46
|
},
|
|
53
47
|
},
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobb-js/lobb-ext-reports",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"license": "
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"license": "UNLICENSED",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
@@ -21,21 +21,22 @@
|
|
|
21
21
|
"test": "bun run test:lobb && bun run test:studio",
|
|
22
22
|
"test:lobb": "bun test extensions/reports/tests",
|
|
23
23
|
"test:studio": "bun --bun playwright test --config extensions/reports/studio/tests/playwright.config.cjs",
|
|
24
|
-
"
|
|
25
|
-
"dev": "
|
|
24
|
+
"dev": "bun run --watch lobb.ts",
|
|
25
|
+
"dev:studio": "vite dev",
|
|
26
|
+
"build": "vite build",
|
|
26
27
|
"start": "bun run lobb.ts",
|
|
27
28
|
"prepare": "svelte-kit sync || echo ''",
|
|
28
29
|
"preview": "vite preview",
|
|
29
30
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"build:studio": "vite build"
|
|
31
|
+
"postinstall": "bun --bun playwright install chromium",
|
|
32
|
+
"prepublishOnly": "lobb-ext-prepublish",
|
|
33
|
+
"postpublish": "lobb-ext-postpublish",
|
|
34
|
+
"package": "svelte-package --input extensions/reports/studio"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@lobb-js/core": "^0.
|
|
37
|
+
"@lobb-js/core": "^0.23.0",
|
|
38
38
|
"chart.js": "^4.4.8",
|
|
39
|
+
"gridstack": "^12.6.0",
|
|
39
40
|
"hono": "^4.7.0",
|
|
40
41
|
"lodash-es": "^4.17.21",
|
|
41
42
|
"openapi-types": "^12.1.3"
|
|
@@ -43,7 +44,7 @@
|
|
|
43
44
|
"devDependencies": {
|
|
44
45
|
"@faker-js/faker": "^9.6.0",
|
|
45
46
|
"@playwright/test": "^1.58.2",
|
|
46
|
-
"@lobb-js/studio": "^0.
|
|
47
|
+
"@lobb-js/studio": "^0.18.1",
|
|
47
48
|
"@lucide/svelte": "^0.563.1",
|
|
48
49
|
"@sveltejs/adapter-node": "^5.5.4",
|
|
49
50
|
"@sveltejs/kit": "^2.55.0",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{}
|