@rebasepro/plugin-insights 0.0.1-canary.eae7889 → 0.1.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/core/src/components/BootstrapAdminBanner.d.ts +4 -0
- package/dist/core/src/components/LoginView/LoginView.d.ts +22 -0
- package/dist/core/src/components/common/useDataTableController.d.ts +3 -3
- package/dist/core/src/components/index.d.ts +1 -0
- package/dist/core/src/hooks/data/useRelationSelector.d.ts +2 -2
- package/dist/core/src/hooks/index.d.ts +1 -0
- package/dist/core/src/hooks/useCollapsedGroups.d.ts +16 -1
- package/dist/core/src/hooks/useResolvedComponent.d.ts +47 -0
- package/dist/index.es.js +314 -214
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +314 -214
- package/dist/index.umd.js.map +1 -1
- package/dist/plugin-insights/src/components/CollectionInsightsInline.d.ts +3 -2
- package/dist/plugin-insights/src/components/InsightWidget.d.ts +4 -1
- package/dist/plugin-insights/src/components/InsightWidgetSkeleton.d.ts +6 -0
- package/dist/plugin-insights/src/engine/useInsightsData.d.ts +2 -2
- package/dist/plugin-insights/src/types/engine.d.ts +10 -1
- package/dist/types/src/controllers/auth.d.ts +8 -2
- package/dist/types/src/controllers/client.d.ts +13 -0
- package/dist/types/src/controllers/collection_registry.d.ts +2 -1
- package/dist/types/src/controllers/data_driver.d.ts +36 -1
- package/dist/types/src/controllers/navigation.d.ts +18 -6
- package/dist/types/src/controllers/registry.d.ts +9 -1
- package/dist/types/src/controllers/side_entity_controller.d.ts +7 -0
- package/dist/types/src/rebase_context.d.ts +17 -0
- package/dist/types/src/types/backend_hooks.d.ts +187 -0
- package/dist/types/src/types/collections.d.ts +31 -11
- package/dist/types/src/types/component_ref.d.ts +47 -0
- package/dist/types/src/types/cron.d.ts +1 -1
- package/dist/types/src/types/entity_views.d.ts +6 -7
- package/dist/types/src/types/formex.d.ts +40 -0
- package/dist/types/src/types/index.d.ts +3 -0
- package/dist/types/src/types/plugins.d.ts +6 -3
- package/dist/types/src/types/properties.d.ts +72 -88
- package/dist/types/src/types/slots.d.ts +20 -10
- package/dist/types/src/types/translations.d.ts +6 -0
- package/dist/ui/src/components/FileUpload.d.ts +1 -1
- package/dist/ui/src/components/SearchBar.d.ts +5 -1
- package/dist/ui/src/styles.d.ts +2 -2
- package/package.json +3 -3
- package/src/components/CollectionInsightsInline.tsx +10 -2
- package/src/components/HomeCardInsightSlot.tsx +5 -1
- package/src/components/InsightWidget.tsx +5 -1
- package/src/components/InsightWidgetSkeleton.tsx +116 -34
- package/src/engine/useInsightsData.ts +5 -5
- package/src/types/engine.ts +11 -1
- package/src/useInsightsPlugin.tsx +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import React, { useRef, useState } from "react";
|
|
2
2
|
import { cls, defaultBorderMixin } from "@rebasepro/ui";
|
|
3
3
|
import type { ScorecardConfig } from "../types";
|
|
4
4
|
|
|
@@ -10,6 +10,12 @@ import type { ScorecardConfig } from "../types";
|
|
|
10
10
|
* The skeleton receives the scorecard config so it can conditionally
|
|
11
11
|
* render placeholder lines for comparison, dateRange, and icon —
|
|
12
12
|
* only when the loaded view will also render them.
|
|
13
|
+
*
|
|
14
|
+
* The standard skeleton mirrors InsightsScorecardView's responsive
|
|
15
|
+
* container-width breakpoints (ResizeObserver → isSmall / isMedium)
|
|
16
|
+
* and uses placeholder heights that exactly match the **computed**
|
|
17
|
+
* Tailwind line-heights (accounting for `leading-*` overrides).
|
|
18
|
+
* This guarantees a pixel-perfect skeleton → loaded transition.
|
|
13
19
|
*/
|
|
14
20
|
export function InsightWidgetSkeleton({
|
|
15
21
|
config,
|
|
@@ -30,7 +36,7 @@ export function InsightWidgetSkeleton({
|
|
|
30
36
|
// Matches InsightsScorecardView compact layout:
|
|
31
37
|
// container: flex flex-col gap-0.5 px-2.5 py-2 rounded-md border
|
|
32
38
|
// title: text-[10px] uppercase → line-height ~14px
|
|
33
|
-
// value row: text-sm font-semibold → line-height
|
|
39
|
+
// value row: text-sm font-semibold → line-height 20px
|
|
34
40
|
// + optional comparison text-[10px] inside value row
|
|
35
41
|
if (compact) {
|
|
36
42
|
return (
|
|
@@ -63,57 +69,133 @@ export function InsightWidgetSkeleton({
|
|
|
63
69
|
}
|
|
64
70
|
|
|
65
71
|
// ── Standard scorecard skeleton ─────────────────────────────────────
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
return <StandardSkeleton
|
|
73
|
+
hasComparison={hasComparison}
|
|
74
|
+
hasIcon={hasIcon}
|
|
75
|
+
hasDateRange={hasDateRange}
|
|
76
|
+
embedded={embedded}
|
|
77
|
+
/>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ── Tailwind line-height reference ──────────────────────────────────────
|
|
81
|
+
// All heights below are the **computed** CSS line-heights, accounting
|
|
82
|
+
// for `leading-*` overrides that InsightsScorecardView applies.
|
|
83
|
+
//
|
|
84
|
+
// Title:
|
|
85
|
+
// text-xs (12px) + leading-snug (1.375) → 12 × 1.375 = 16.5px
|
|
86
|
+
// text-[11px] + leading-snug (1.375) → 11 × 1.375 = 15.125px
|
|
87
|
+
//
|
|
88
|
+
// DateRange:
|
|
89
|
+
// text-[10px] with no explicit LH → normal ≈ 14px (browser)
|
|
90
|
+
//
|
|
91
|
+
// Value:
|
|
92
|
+
// text-2xl (24px) + leading-tight (1.25) → 24 × 1.25 = 30px
|
|
93
|
+
// text-xl (20px) + leading-tight (1.25) → 20 × 1.25 = 25px
|
|
94
|
+
// text-lg (18px) + leading-tight (1.25) → 18 × 1.25 = 22.5px
|
|
95
|
+
//
|
|
96
|
+
// Comparison:
|
|
97
|
+
// text-xs (12px) → built-in LH 1rem = 16px
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Inner component for the standard scorecard skeleton.
|
|
101
|
+
*
|
|
102
|
+
* Mirrors InsightsScorecardView's layout by:
|
|
103
|
+
* 1. Using the same ResizeObserver + containerWidth pattern for
|
|
104
|
+
* responsive breakpoints (isSmall < 200px, isMedium < 300px).
|
|
105
|
+
* 2. Using placeholder heights derived from the exact computed
|
|
106
|
+
* Tailwind line-heights that InsightsScorecardView renders.
|
|
107
|
+
* 3. Matching all container classes, margins, paddings, and flex
|
|
108
|
+
* layout properties identically.
|
|
109
|
+
*/
|
|
110
|
+
function StandardSkeleton({
|
|
111
|
+
hasComparison,
|
|
112
|
+
hasIcon,
|
|
113
|
+
hasDateRange,
|
|
114
|
+
embedded,
|
|
115
|
+
}: {
|
|
116
|
+
hasComparison: boolean;
|
|
117
|
+
hasIcon: boolean;
|
|
118
|
+
hasDateRange: boolean;
|
|
119
|
+
embedded: boolean;
|
|
120
|
+
}) {
|
|
121
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
122
|
+
const [containerWidth, setContainerWidth] = useState<number | null>(null);
|
|
123
|
+
|
|
124
|
+
React.useLayoutEffect(() => {
|
|
125
|
+
if (!containerRef.current) return;
|
|
126
|
+
setContainerWidth(containerRef.current.offsetWidth);
|
|
127
|
+
const observer = new ResizeObserver((entries) => {
|
|
128
|
+
for (const entry of entries) {
|
|
129
|
+
setContainerWidth(entry.contentRect.width);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
observer.observe(containerRef.current);
|
|
133
|
+
return () => observer.disconnect();
|
|
134
|
+
}, []);
|
|
135
|
+
|
|
136
|
+
// Mirror InsightsScorecardView's responsive breakpoints exactly
|
|
137
|
+
const isSmall = containerWidth !== null && containerWidth < 200;
|
|
138
|
+
|
|
139
|
+
// Computed line-heights for each breakpoint
|
|
140
|
+
// Title: text-xs + leading-snug = 16.5px, text-[11px] + leading-snug = 15.125px
|
|
141
|
+
const titleHeight = isSmall ? 15 : 16.5;
|
|
142
|
+
// Value: leading-tight (×1.25) applied on top of font-size
|
|
143
|
+
const valueHeight = isSmall
|
|
144
|
+
? 22.5 // text-lg: 18 × 1.25
|
|
145
|
+
: (containerWidth !== null && containerWidth < 300)
|
|
146
|
+
? 25 // text-xl: 20 × 1.25
|
|
147
|
+
: 30; // text-2xl: 24 × 1.25
|
|
148
|
+
// Comparison: text-xs = 12px / 16px line-height (no leading override)
|
|
149
|
+
const comparisonHeight = 16;
|
|
150
|
+
// Icon: 14px when small, 18px otherwise
|
|
151
|
+
const iconSize = isSmall ? 14 : 18;
|
|
152
|
+
|
|
153
|
+
const baseClass = embedded
|
|
154
|
+
? `flex flex-col min-w-0 h-full ${isSmall ? "px-3.5 py-3" : "px-5 py-4"}`
|
|
155
|
+
: cls("rounded-lg flex flex-col min-w-0 bg-transparent border", defaultBorderMixin, isSmall ? "px-3.5 py-3" : "px-5 py-4");
|
|
156
|
+
|
|
74
157
|
return (
|
|
75
158
|
<div
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
? "h-full px-5 py-4"
|
|
80
|
-
: "rounded-lg bg-transparent border px-5 py-4",
|
|
81
|
-
!embedded && defaultBorderMixin
|
|
82
|
-
)}
|
|
83
|
-
style={embedded ? undefined : { minHeight: 92 }}
|
|
159
|
+
ref={containerRef}
|
|
160
|
+
className={cls("animate-pulse", baseClass)}
|
|
161
|
+
style={embedded ? undefined : { minHeight: isSmall ? 68 : 92 }}
|
|
84
162
|
>
|
|
85
|
-
{/* Title row */}
|
|
86
|
-
<div className=
|
|
163
|
+
{/* Title row — identical flex structure to InsightsScorecardView */}
|
|
164
|
+
<div className={`flex items-center justify-between ${isSmall ? "mb-1" : "mb-2"}`}>
|
|
87
165
|
<div className="flex flex-col min-w-0">
|
|
88
|
-
{/* Title */}
|
|
166
|
+
{/* Title placeholder */}
|
|
89
167
|
<div className="bg-surface-200 dark:bg-surface-700 rounded"
|
|
90
|
-
style={{ height:
|
|
168
|
+
style={{ height: titleHeight, width: "60%" }}
|
|
91
169
|
/>
|
|
92
|
-
{/* DateRange
|
|
93
|
-
{hasDateRange && (
|
|
170
|
+
{/* DateRange — hidden when isSmall, same as real view (line 134) */}
|
|
171
|
+
{hasDateRange && !isSmall && (
|
|
94
172
|
<div className="bg-surface-200/60 dark:bg-surface-700/60 rounded mt-0.5"
|
|
95
173
|
style={{ height: 14, width: "40%" }}
|
|
96
174
|
/>
|
|
97
175
|
)}
|
|
98
176
|
</div>
|
|
99
|
-
{/* Icon placeholder
|
|
177
|
+
{/* Icon placeholder — same wrapper as real view */}
|
|
100
178
|
{hasIcon && (
|
|
101
|
-
<
|
|
102
|
-
|
|
103
|
-
|
|
179
|
+
<span className="ml-2 shrink-0">
|
|
180
|
+
<div className="bg-surface-200 dark:bg-surface-700 rounded"
|
|
181
|
+
style={{ height: iconSize, width: iconSize }}
|
|
182
|
+
/>
|
|
183
|
+
</span>
|
|
104
184
|
)}
|
|
105
185
|
</div>
|
|
106
186
|
|
|
107
|
-
{/* Main value */}
|
|
187
|
+
{/* Main value placeholder */}
|
|
108
188
|
<div className="bg-surface-200 dark:bg-surface-700 rounded"
|
|
109
|
-
style={{ height:
|
|
189
|
+
style={{ height: valueHeight, width: "40%" }}
|
|
110
190
|
/>
|
|
111
191
|
|
|
112
|
-
{/* Comparison
|
|
192
|
+
{/* Comparison placeholder */}
|
|
113
193
|
{hasComparison && (
|
|
114
|
-
<div className="
|
|
115
|
-
|
|
116
|
-
|
|
194
|
+
<div className={isSmall ? "mt-0.5" : "mt-1"}>
|
|
195
|
+
<div className="bg-surface-200/60 dark:bg-surface-700/60 rounded"
|
|
196
|
+
style={{ height: comparisonHeight, width: "25%" }}
|
|
197
|
+
/>
|
|
198
|
+
</div>
|
|
117
199
|
)}
|
|
118
200
|
</div>
|
|
119
201
|
);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useEffect, useState } from "react";
|
|
2
|
-
import type { InsightDefinition, InsightDataResult } from "../types";
|
|
2
|
+
import type { InsightDefinition, InsightDataResult, InsightContext } from "../types";
|
|
3
3
|
import { useInsightsEngine } from "./InsightsProvider";
|
|
4
4
|
import { useAuthController } from "@rebasepro/core";
|
|
5
5
|
|
|
@@ -16,7 +16,7 @@ import { useAuthController } from "@rebasepro/core";
|
|
|
16
16
|
*/
|
|
17
17
|
export function useInsightsData(
|
|
18
18
|
definition: InsightDefinition,
|
|
19
|
-
|
|
19
|
+
context: InsightContext
|
|
20
20
|
): {
|
|
21
21
|
data: InsightDataResult | null;
|
|
22
22
|
loading: boolean;
|
|
@@ -30,7 +30,7 @@ export function useInsightsData(
|
|
|
30
30
|
const [loading, setLoading] = useState(true);
|
|
31
31
|
const [error, setError] = useState<Error | null>(null);
|
|
32
32
|
|
|
33
|
-
const cacheKey = `${definition.id}:${collectionSlug ?? "global"}`;
|
|
33
|
+
const cacheKey = `${definition.id}:${context.path ?? context.collectionSlug ?? "global"}`;
|
|
34
34
|
|
|
35
35
|
useEffect(() => {
|
|
36
36
|
// Keep showing skeleton until both auth and engine are ready
|
|
@@ -71,7 +71,7 @@ export function useInsightsData(
|
|
|
71
71
|
setLoading(true);
|
|
72
72
|
setError(null);
|
|
73
73
|
|
|
74
|
-
const promise = definition.data();
|
|
74
|
+
const promise = definition.data(context);
|
|
75
75
|
|
|
76
76
|
cache.setInflight(cacheKey, promise);
|
|
77
77
|
|
|
@@ -94,7 +94,7 @@ export function useInsightsData(
|
|
|
94
94
|
return () => {
|
|
95
95
|
cancelled = true;
|
|
96
96
|
};
|
|
97
|
-
}, [definition.id, definition.data, collectionSlug, cacheKey, cache, authReady]);
|
|
97
|
+
}, [definition.id, definition.data, context.path, context.collectionSlug, cacheKey, cache, authReady]);
|
|
98
98
|
|
|
99
99
|
return { data, loading, error };
|
|
100
100
|
}
|
package/src/types/engine.ts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import type { DataRow, ScorecardConfig } from "./widgets";
|
|
2
2
|
|
|
3
|
+
export interface InsightContext {
|
|
4
|
+
/** The resolved path of the collection (e.g., "products/123/orders" or "orders") */
|
|
5
|
+
path?: string;
|
|
6
|
+
parentCollectionSlugs?: string[];
|
|
7
|
+
/** The parent entity IDs if this is a subcollection (e.g., ["123"]) */
|
|
8
|
+
parentEntityIds?: string[];
|
|
9
|
+
/** The collection slug if this is an insight at the collection level */
|
|
10
|
+
collectionSlug?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
3
13
|
/**
|
|
4
14
|
* Result returned by an insight's data callback.
|
|
5
15
|
*/
|
|
@@ -45,7 +55,7 @@ export interface InsightDefinition {
|
|
|
45
55
|
* }
|
|
46
56
|
* ```
|
|
47
57
|
*/
|
|
48
|
-
data: () => Promise<InsightDataResult>;
|
|
58
|
+
data: (context: InsightContext) => Promise<InsightDataResult>;
|
|
49
59
|
|
|
50
60
|
/** Scorecard field mapping + formatting. */
|
|
51
61
|
scorecard: ScorecardConfig;
|
|
@@ -77,7 +77,7 @@ export function useInsightsPlugin(config: InsightsPluginConfig): RebasePlugin {
|
|
|
77
77
|
if (collectionSlug !== slug) return null;
|
|
78
78
|
return (
|
|
79
79
|
<CollectionInsightsInline
|
|
80
|
-
{...props as { path: string; collection: unknown;
|
|
80
|
+
{...props as { path: string; collection: unknown; parentCollectionSlugs: string[], parentEntityIds: string[] }}
|
|
81
81
|
insights={collectionInsights}
|
|
82
82
|
/>
|
|
83
83
|
);
|