@izumisy-tailor/tailor-data-viewer 0.1.30 → 0.1.32
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/README.md +45 -18
- package/docs/API.md +86 -7
- package/docs/app-shell-module.md +6 -2
- package/docs/compositional-api.md +14 -6
- package/package.json +1 -1
- package/src/app-shell/create-data-view-module.tsx +7 -4
- package/src/component/column-selector.test.tsx +2 -1
- package/src/component/contexts/data-viewer-context.test.tsx +3 -2
- package/src/component/contexts/data-viewer-context.tsx +7 -6
- package/src/component/contexts/table-data-context.tsx +7 -6
- package/src/component/create-data-viewer.tsx +70 -11
- package/src/component/data-table-toolbar.test.tsx +31 -16
- package/src/component/data-table.tsx +3 -3
- package/src/component/data-view-tab-content.tsx +4 -3
- package/src/component/data-viewer.tsx +5 -4
- package/src/component/hooks/table-data-store.ts +10 -10
- package/src/component/hooks/use-relation-data.ts +24 -18
- package/src/component/hooks/use-table-data.test.ts +29 -40
- package/src/component/hooks/use-table-data.ts +5 -4
- package/src/component/index.ts +11 -3
- package/src/component/search-filter.test.tsx +2 -1
- package/src/component/single-record-tab-content.test.tsx +45 -32
- package/src/component/single-record-tab-content.tsx +16 -17
- package/src/graphql/fetcher.ts +221 -0
- package/src/graphql/index.ts +2 -0
- package/src/graphql/{graphql-fetcher.ts → single-record-fetcher.ts} +27 -13
- package/src/store/tailordb/index.ts +35 -19
- package/src/store/types.ts +2 -2
- package/src/tests/helper.ts +14 -0
- /package/src/{test-setup.ts → tests/setup.ts} +0 -0
package/README.md
CHANGED
|
@@ -45,12 +45,16 @@ For Tailor Platform apps using `@tailor-platform/app-shell`:
|
|
|
45
45
|
|
|
46
46
|
```typescript
|
|
47
47
|
// src/shared/data-viewer.ts
|
|
48
|
-
import { createDataViewer } from "@izumisy-tailor/tailor-data-viewer/component";
|
|
48
|
+
import { createDataViewer, createDefaultFetcher } from "@izumisy-tailor/tailor-data-viewer/component";
|
|
49
49
|
import { tableMetadata } from "./generated/table-metadata";
|
|
50
50
|
|
|
51
|
+
const fetcher = createDefaultFetcher({
|
|
52
|
+
endpoint: import.meta.env.VITE_APP_URL,
|
|
53
|
+
});
|
|
54
|
+
|
|
51
55
|
export const DataViewer = createDataViewer({
|
|
52
56
|
metadata: tableMetadata,
|
|
53
|
-
|
|
57
|
+
fetcher,
|
|
54
58
|
});
|
|
55
59
|
```
|
|
56
60
|
|
|
@@ -80,28 +84,47 @@ export const App = () => (
|
|
|
80
84
|
|
|
81
85
|
For detailed options, see [AppShell Module Integration](./docs/app-shell-module.md).
|
|
82
86
|
|
|
83
|
-
### Standalone
|
|
87
|
+
### Standalone Explorer View
|
|
84
88
|
|
|
85
|
-
For custom React apps,
|
|
89
|
+
For custom React apps without AppShell, use `DataViewer.ExplorerView`. This provides a tab-based UI where users can select tables, create filters, and save views:
|
|
86
90
|
|
|
87
91
|
```tsx
|
|
88
|
-
|
|
92
|
+
// src/lib/data-viewer.ts (create once per app)
|
|
93
|
+
import { createDataViewer, createDefaultFetcher } from "@izumisy-tailor/tailor-data-viewer/component";
|
|
89
94
|
import { createIndexedDBStore } from "@izumisy-tailor/tailor-data-viewer/store/indexeddb";
|
|
90
|
-
import "@izumisy-tailor/tailor-data-viewer/styles/theme.css";
|
|
91
|
-
|
|
92
|
-
// Import the generated metadata from your Tailor Platform SDK project
|
|
93
95
|
import { tableMetadata } from "./generated/table-metadata";
|
|
94
96
|
|
|
95
|
-
const
|
|
97
|
+
const fetcher = createDefaultFetcher({
|
|
98
|
+
endpoint: "https://your-app.tailor.tech/graphql",
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
export const DataViewer = createDataViewer({
|
|
102
|
+
metadata: tableMetadata,
|
|
103
|
+
fetcher,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
export const savedViewStore = createIndexedDBStore();
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
// src/App.tsx
|
|
111
|
+
import "@izumisy-tailor/tailor-data-viewer/styles/theme.css";
|
|
112
|
+
import { DataViewer, savedViewStore } from "./lib/data-viewer";
|
|
96
113
|
|
|
97
114
|
function App() {
|
|
98
115
|
return (
|
|
99
|
-
<
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
116
|
+
<DataViewer.ExplorerView savedViewStore={savedViewStore} />
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// With initial view loaded from URL
|
|
121
|
+
function AppWithViewId() {
|
|
122
|
+
const viewId = useSearchParams().get("viewId") ?? undefined;
|
|
123
|
+
return (
|
|
124
|
+
<DataViewer.ExplorerView
|
|
125
|
+
savedViewStore={savedViewStore}
|
|
126
|
+
initialViewId={viewId}
|
|
127
|
+
/>
|
|
105
128
|
);
|
|
106
129
|
}
|
|
107
130
|
```
|
|
@@ -110,16 +133,20 @@ For store options, see [Saved View Store](./docs/saved-view-store.md).
|
|
|
110
133
|
|
|
111
134
|
### Compositional API
|
|
112
135
|
|
|
113
|
-
For more control over the UI layout and behavior, use the compositional (compound component) API
|
|
136
|
+
For more control over the UI layout and behavior, use the compositional (compound component) API with `createDataViewer`:
|
|
114
137
|
|
|
115
138
|
```tsx
|
|
116
139
|
// src/shared/data-viewer.ts
|
|
117
|
-
import { createDataViewer } from "@izumisy-tailor/tailor-data-viewer/component";
|
|
140
|
+
import { createDataViewer, createDefaultFetcher } from "@izumisy-tailor/tailor-data-viewer/component";
|
|
118
141
|
import { tableMetadata } from "./generated/table-metadata";
|
|
119
142
|
|
|
143
|
+
const fetcher = createDefaultFetcher({
|
|
144
|
+
endpoint: "https://your-app.tailor.tech",
|
|
145
|
+
});
|
|
146
|
+
|
|
120
147
|
export const DataViewer = createDataViewer({
|
|
121
148
|
metadata: tableMetadata,
|
|
122
|
-
|
|
149
|
+
fetcher,
|
|
123
150
|
});
|
|
124
151
|
```
|
|
125
152
|
|
package/docs/API.md
CHANGED
|
@@ -4,21 +4,100 @@
|
|
|
4
4
|
|
|
5
5
|
### `createDataViewer`
|
|
6
6
|
|
|
7
|
-
Creates a DataViewer instance with fixed metadata and
|
|
7
|
+
Creates a DataViewer instance with fixed metadata and fetcher. Call this once per app and share the instance across pages.
|
|
8
8
|
|
|
9
9
|
```tsx
|
|
10
|
-
import { createDataViewer } from "@izumisy-tailor/tailor-data-viewer/component";
|
|
10
|
+
import { createDataViewer, createDefaultFetcher } from "@izumisy-tailor/tailor-data-viewer/component";
|
|
11
|
+
|
|
12
|
+
const fetcher = createDefaultFetcher({
|
|
13
|
+
endpoint: "https://your-app.tailor.tech",
|
|
14
|
+
});
|
|
11
15
|
|
|
12
16
|
const DataViewer = createDataViewer({
|
|
13
17
|
metadata: tableMetadata,
|
|
14
|
-
|
|
18
|
+
fetcher,
|
|
15
19
|
});
|
|
16
20
|
|
|
17
21
|
// Returns:
|
|
18
22
|
// - DataViewer.TableDataProvider - Root provider for table data
|
|
19
23
|
// - DataViewer.ToolbarProvider - Toolbar state provider
|
|
20
24
|
// - DataViewer.metadata - The metadata passed to createDataViewer
|
|
21
|
-
// - DataViewer.
|
|
25
|
+
// - DataViewer.fetcher - The fetcher passed to createDataViewer
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## GraphQL Fetcher
|
|
29
|
+
|
|
30
|
+
### `GraphQLFetcher`
|
|
31
|
+
|
|
32
|
+
Interface for GraphQL data fetching. Implement this interface to use your own GraphQL client.
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
interface GraphQLFetcher {
|
|
36
|
+
execute: <T = unknown>(
|
|
37
|
+
query: string,
|
|
38
|
+
variables?: Record<string, unknown>,
|
|
39
|
+
options?: GraphQLFetcherOptions,
|
|
40
|
+
) => Promise<GraphQLFetcherResult<T>>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface GraphQLFetcherOptions {
|
|
44
|
+
signal?: AbortSignal;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface GraphQLFetcherResult<T> {
|
|
48
|
+
data: T | null;
|
|
49
|
+
errors?: GraphQLError[];
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### `createDefaultFetcher`
|
|
54
|
+
|
|
55
|
+
Creates a default GraphQL fetcher using `graphql-request`.
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
import { createDefaultFetcher } from "@izumisy-tailor/tailor-data-viewer/component";
|
|
59
|
+
|
|
60
|
+
const fetcher = createDefaultFetcher({
|
|
61
|
+
endpoint: "https://your-app.tailor.tech/graphql",
|
|
62
|
+
headers: { "X-Custom-Header": "value" }, // optional
|
|
63
|
+
credentials: "include", // optional, default: "include"
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### `createUrqlFetcher`
|
|
68
|
+
|
|
69
|
+
Creates a GraphQL fetcher from an existing urql Client.
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
import { createClient } from "@urql/core";
|
|
73
|
+
import { createUrqlFetcher } from "@izumisy-tailor/tailor-data-viewer/component";
|
|
74
|
+
|
|
75
|
+
const urqlClient = createClient({
|
|
76
|
+
url: "https://your-app.tailor.tech/graphql",
|
|
77
|
+
fetchOptions: {
|
|
78
|
+
headers: { "Authorization": "Bearer token" },
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const fetcher = createUrqlFetcher({ client: urqlClient });
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Custom Fetcher Implementation
|
|
86
|
+
|
|
87
|
+
You can implement your own fetcher for any GraphQL client:
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
const customFetcher: GraphQLFetcher = {
|
|
91
|
+
execute: async (query, variables, options) => {
|
|
92
|
+
const response = await fetch("/graphql", {
|
|
93
|
+
method: "POST",
|
|
94
|
+
headers: { "Content-Type": "application/json" },
|
|
95
|
+
body: JSON.stringify({ query, variables }),
|
|
96
|
+
signal: options?.signal,
|
|
97
|
+
});
|
|
98
|
+
return response.json();
|
|
99
|
+
},
|
|
100
|
+
};
|
|
22
101
|
```
|
|
23
102
|
|
|
24
103
|
## Components
|
|
@@ -31,8 +110,8 @@ Main component with tab-based interface (for AppShell integration).
|
|
|
31
110
|
interface DataViewerProps {
|
|
32
111
|
/** Map of table name to metadata */
|
|
33
112
|
tableMetadata: TableMetadataMap;
|
|
34
|
-
/** GraphQL
|
|
35
|
-
|
|
113
|
+
/** GraphQL fetcher for data fetching */
|
|
114
|
+
fetcher: GraphQLFetcher;
|
|
36
115
|
/** Optional initial view ID to load */
|
|
37
116
|
initialViewId?: string;
|
|
38
117
|
}
|
|
@@ -88,7 +167,7 @@ const {
|
|
|
88
167
|
previousPage,
|
|
89
168
|
resetPagination,
|
|
90
169
|
refetch,
|
|
91
|
-
} = useTableData(
|
|
170
|
+
} = useTableData(fetcher, table, selectedFields, selectedRelations, filters, metadataMap, expandedFields);
|
|
92
171
|
```
|
|
93
172
|
|
|
94
173
|
### `useColumnState`
|
package/docs/app-shell-module.md
CHANGED
|
@@ -14,12 +14,16 @@ First, create a shared DataViewer instance:
|
|
|
14
14
|
|
|
15
15
|
```typescript
|
|
16
16
|
// src/shared/data-viewer.ts
|
|
17
|
-
import { createDataViewer } from "@izumisy-tailor/tailor-data-viewer/component";
|
|
17
|
+
import { createDataViewer, createDefaultFetcher } from "@izumisy-tailor/tailor-data-viewer/component";
|
|
18
18
|
import { tableMetadata } from "@tailor-platform/my-app/generated/table-metadata";
|
|
19
19
|
|
|
20
|
+
const fetcher = createDefaultFetcher({
|
|
21
|
+
endpoint: import.meta.env.VITE_APP_URL,
|
|
22
|
+
});
|
|
23
|
+
|
|
20
24
|
export const DataViewer = createDataViewer({
|
|
21
25
|
metadata: tableMetadata,
|
|
22
|
-
|
|
26
|
+
fetcher,
|
|
23
27
|
});
|
|
24
28
|
```
|
|
25
29
|
|
|
@@ -18,12 +18,16 @@ First, create a shared DataViewer instance (once per app):
|
|
|
18
18
|
|
|
19
19
|
```tsx
|
|
20
20
|
// src/shared/data-viewer.ts
|
|
21
|
-
import { createDataViewer } from "@izumisy-tailor/tailor-data-viewer/component";
|
|
21
|
+
import { createDataViewer, createDefaultFetcher } from "@izumisy-tailor/tailor-data-viewer/component";
|
|
22
22
|
import { tableMetadata } from "./generated/table-metadata";
|
|
23
23
|
|
|
24
|
+
const fetcher = createDefaultFetcher({
|
|
25
|
+
endpoint: "https://your-app.tailor.tech",
|
|
26
|
+
});
|
|
27
|
+
|
|
24
28
|
export const DataViewer = createDataViewer({
|
|
25
29
|
metadata: tableMetadata,
|
|
26
|
-
|
|
30
|
+
fetcher,
|
|
27
31
|
});
|
|
28
32
|
```
|
|
29
33
|
|
|
@@ -70,21 +74,25 @@ function MyDataViewer() {
|
|
|
70
74
|
|
|
71
75
|
### createDataViewer
|
|
72
76
|
|
|
73
|
-
Factory function to create a DataViewer instance with fixed metadata and
|
|
77
|
+
Factory function to create a DataViewer instance with fixed metadata and fetcher. Call this once per app and share the instance across pages.
|
|
74
78
|
|
|
75
79
|
```tsx
|
|
76
|
-
import { createDataViewer } from "@izumisy-tailor/tailor-data-viewer/component";
|
|
80
|
+
import { createDataViewer, createDefaultFetcher } from "@izumisy-tailor/tailor-data-viewer/component";
|
|
81
|
+
|
|
82
|
+
const fetcher = createDefaultFetcher({
|
|
83
|
+
endpoint: "https://your-app.tailor.tech",
|
|
84
|
+
});
|
|
77
85
|
|
|
78
86
|
const DataViewer = createDataViewer({
|
|
79
87
|
metadata: tableMetadata, // Generated from TailorDB schema
|
|
80
|
-
|
|
88
|
+
fetcher,
|
|
81
89
|
});
|
|
82
90
|
|
|
83
91
|
// Returns:
|
|
84
92
|
// - DataViewer.TableDataProvider - Root provider for table data
|
|
85
93
|
// - DataViewer.ToolbarProvider - Toolbar state provider
|
|
86
94
|
// - DataViewer.metadata - The metadata passed to createDataViewer
|
|
87
|
-
// - DataViewer.
|
|
95
|
+
// - DataViewer.fetcher - The fetcher passed to createDataViewer
|
|
88
96
|
```
|
|
89
97
|
|
|
90
98
|
### DataViewer.TableDataProvider
|
package/package.json
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
Columns,
|
|
14
14
|
LayoutGrid,
|
|
15
15
|
} from "lucide-react";
|
|
16
|
+
import type { TableMetadataMap } from "../generator/metadata-generator";
|
|
16
17
|
import type { DataViewModuleConfig } from "./types";
|
|
17
18
|
import { isStoreFactory } from "./types";
|
|
18
19
|
import { DataViewer as DataViewerComponent } from "../component/data-viewer";
|
|
@@ -45,7 +46,9 @@ import { Badge } from "../component/ui/badge";
|
|
|
45
46
|
* });
|
|
46
47
|
* ```
|
|
47
48
|
*/
|
|
48
|
-
export function createDataViewModule
|
|
49
|
+
export function createDataViewModule<
|
|
50
|
+
TMetadata extends TableMetadataMap = TableMetadataMap,
|
|
51
|
+
>(config: DataViewModuleConfig<TMetadata>) {
|
|
49
52
|
const {
|
|
50
53
|
dataViewer,
|
|
51
54
|
savedViewStore: storeOrFactory,
|
|
@@ -53,7 +56,7 @@ export function createDataViewModule(config: DataViewModuleConfig) {
|
|
|
53
56
|
meta = {},
|
|
54
57
|
} = config;
|
|
55
58
|
|
|
56
|
-
const { metadata: tableMetadata,
|
|
59
|
+
const { metadata: tableMetadata, fetcher } = dataViewer;
|
|
57
60
|
|
|
58
61
|
const basePath = path.base ?? "data-view";
|
|
59
62
|
const explorerPath = path.explorer ?? "explorer";
|
|
@@ -62,7 +65,7 @@ export function createDataViewModule(config: DataViewModuleConfig) {
|
|
|
62
65
|
|
|
63
66
|
// Resolve store (call factory if needed)
|
|
64
67
|
const store = isStoreFactory(storeOrFactory)
|
|
65
|
-
? storeOrFactory({
|
|
68
|
+
? storeOrFactory({ fetcher })
|
|
66
69
|
: storeOrFactory;
|
|
67
70
|
|
|
68
71
|
// Create the explorer resource page component
|
|
@@ -74,7 +77,7 @@ export function createDataViewModule(config: DataViewModuleConfig) {
|
|
|
74
77
|
<SavedViewProvider store={store}>
|
|
75
78
|
<DataViewerComponent
|
|
76
79
|
metadata={tableMetadata}
|
|
77
|
-
|
|
80
|
+
fetcher={fetcher}
|
|
78
81
|
initialViewId={viewId}
|
|
79
82
|
/>
|
|
80
83
|
</SavedViewProvider>
|
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
import { DataViewerProvider } from "./contexts";
|
|
12
12
|
import { ToolbarProvider } from "./contexts";
|
|
13
13
|
import type { ReactNode } from "react";
|
|
14
|
+
import { createMockFetcher } from "../tests/helper";
|
|
14
15
|
|
|
15
16
|
const mockFields: FieldMetadata[] = [
|
|
16
17
|
{ name: "id", type: "uuid", required: true, description: "ID" },
|
|
@@ -48,7 +49,7 @@ function TestWrapper({
|
|
|
48
49
|
}: WrapperProps) {
|
|
49
50
|
return (
|
|
50
51
|
<DataViewerProvider
|
|
51
|
-
|
|
52
|
+
fetcher={createMockFetcher()}
|
|
52
53
|
tableName={tableMetadata.name}
|
|
53
54
|
metadata={metadata}
|
|
54
55
|
initialData={{
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
type DataViewerProviderProps,
|
|
5
5
|
} from "./data-viewer-context";
|
|
6
6
|
import type { TableMetadataMap } from "../../generator/metadata-generator";
|
|
7
|
+
import { createMockFetcher } from "../../tests/helper";
|
|
7
8
|
|
|
8
9
|
// =============================================================================
|
|
9
10
|
// Mock metadata with `as const` for type-safe tests
|
|
@@ -135,14 +136,14 @@ describe("DataViewerProvider type safety", () => {
|
|
|
135
136
|
children: null,
|
|
136
137
|
tableName: "user",
|
|
137
138
|
metadata: mockMetadata,
|
|
138
|
-
|
|
139
|
+
fetcher: createMockFetcher(),
|
|
139
140
|
};
|
|
140
141
|
|
|
141
142
|
const postProps: DataViewerProviderProps<MockMetadata, "post"> = {
|
|
142
143
|
children: null,
|
|
143
144
|
tableName: "post",
|
|
144
145
|
metadata: mockMetadata,
|
|
145
|
-
|
|
146
|
+
fetcher: createMockFetcher(),
|
|
146
147
|
};
|
|
147
148
|
|
|
148
149
|
expect(userProps.tableName).toBe("user");
|
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
TableMetadataMap,
|
|
12
12
|
ExpandedRelationFields,
|
|
13
13
|
} from "../../generator/metadata-generator";
|
|
14
|
+
import type { GraphQLFetcher } from "../../graphql/fetcher";
|
|
14
15
|
import { useColumnState } from "../hooks/use-column-state";
|
|
15
16
|
import type { SearchFilters } from "../types";
|
|
16
17
|
import type { SortState } from "../hooks/use-table-data";
|
|
@@ -71,7 +72,7 @@ export interface DataViewerContextValue {
|
|
|
71
72
|
// Metadata
|
|
72
73
|
tableMetadata: TableMetadata | null;
|
|
73
74
|
metadata: TableMetadataMap;
|
|
74
|
-
|
|
75
|
+
fetcher: GraphQLFetcher;
|
|
75
76
|
|
|
76
77
|
// Column state
|
|
77
78
|
selectedFields: string[];
|
|
@@ -148,8 +149,8 @@ export interface DataViewerProviderProps<
|
|
|
148
149
|
tableName: TTableName;
|
|
149
150
|
/** All table metadata (generated from TailorDB schema) */
|
|
150
151
|
metadata: TMetadata;
|
|
151
|
-
/**
|
|
152
|
-
|
|
152
|
+
/** GraphQL fetcher for data fetching */
|
|
153
|
+
fetcher: GraphQLFetcher;
|
|
153
154
|
/** Initial data for filters, selected fields, and relations (type-safe when metadata is `as const`) */
|
|
154
155
|
initialData?: DataViewerInitialData<TMetadata, TTableName>;
|
|
155
156
|
}
|
|
@@ -165,7 +166,7 @@ export function DataViewerProvider<
|
|
|
165
166
|
children,
|
|
166
167
|
tableName,
|
|
167
168
|
metadata,
|
|
168
|
-
|
|
169
|
+
fetcher,
|
|
169
170
|
initialData,
|
|
170
171
|
}: DataViewerProviderProps<TMetadata, TTableName>) {
|
|
171
172
|
// Get table metadata from the map
|
|
@@ -213,7 +214,7 @@ export function DataViewerProvider<
|
|
|
213
214
|
() => ({
|
|
214
215
|
tableMetadata,
|
|
215
216
|
metadata,
|
|
216
|
-
|
|
217
|
+
fetcher,
|
|
217
218
|
selectedFields: columnState.selectedFields,
|
|
218
219
|
toggleField: columnState.toggleField,
|
|
219
220
|
selectAllFields: columnState.selectAll,
|
|
@@ -234,7 +235,7 @@ export function DataViewerProvider<
|
|
|
234
235
|
[
|
|
235
236
|
tableMetadata,
|
|
236
237
|
metadata,
|
|
237
|
-
|
|
238
|
+
fetcher,
|
|
238
239
|
columnState.selectedFields,
|
|
239
240
|
columnState.toggleField,
|
|
240
241
|
columnState.selectAll,
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
TableMetadataMap,
|
|
10
10
|
ExpandedRelationFields,
|
|
11
11
|
} from "../../generator/metadata-generator";
|
|
12
|
+
import type { GraphQLFetcher } from "../../graphql/fetcher";
|
|
12
13
|
import {
|
|
13
14
|
useTableData,
|
|
14
15
|
type SortState,
|
|
@@ -54,8 +55,8 @@ export interface TableDataProviderProps<
|
|
|
54
55
|
tableName: TTableName;
|
|
55
56
|
/** All table metadata (generated from TailorDB schema) */
|
|
56
57
|
metadata: TMetadata;
|
|
57
|
-
/**
|
|
58
|
-
|
|
58
|
+
/** GraphQL fetcher for data fetching */
|
|
59
|
+
fetcher: GraphQLFetcher;
|
|
59
60
|
/** Initial data for filters, selected fields, and relations (type-safe when metadata is `as const`) */
|
|
60
61
|
initialData?: DataViewerInitialData<TMetadata, TTableName>;
|
|
61
62
|
}
|
|
@@ -71,14 +72,14 @@ export function TableDataProvider<
|
|
|
71
72
|
children,
|
|
72
73
|
tableName,
|
|
73
74
|
metadata,
|
|
74
|
-
|
|
75
|
+
fetcher,
|
|
75
76
|
initialData,
|
|
76
77
|
}: TableDataProviderProps<TMetadata, TTableName>) {
|
|
77
78
|
return (
|
|
78
79
|
<DataViewerProvider
|
|
79
80
|
tableName={tableName}
|
|
80
81
|
metadata={metadata}
|
|
81
|
-
|
|
82
|
+
fetcher={fetcher}
|
|
82
83
|
initialData={initialData}
|
|
83
84
|
>
|
|
84
85
|
<TableDataProviderInner>{children}</TableDataProviderInner>
|
|
@@ -92,7 +93,7 @@ export function TableDataProvider<
|
|
|
92
93
|
*/
|
|
93
94
|
function TableDataProviderInner({ children }: { children: ReactNode }) {
|
|
94
95
|
const {
|
|
95
|
-
|
|
96
|
+
fetcher,
|
|
96
97
|
tableMetadata,
|
|
97
98
|
metadata,
|
|
98
99
|
selectedFields,
|
|
@@ -105,7 +106,7 @@ function TableDataProviderInner({ children }: { children: ReactNode }) {
|
|
|
105
106
|
|
|
106
107
|
// Fetch table data
|
|
107
108
|
const tableData = useTableData(
|
|
108
|
-
|
|
109
|
+
fetcher,
|
|
109
110
|
tableMetadata,
|
|
110
111
|
selectedFields,
|
|
111
112
|
selectedRelations,
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import type { TableMetadataMap } from "../generator/metadata-generator";
|
|
3
|
+
import type { GraphQLFetcher } from "../graphql/fetcher";
|
|
4
|
+
import type { SavedViewStore, SavedViewStoreFactory } from "../store/types";
|
|
3
5
|
import { type DataViewerInitialData } from "./contexts/data-viewer-context";
|
|
4
6
|
import { TableDataProvider } from "./contexts/table-data-context";
|
|
5
7
|
import { ToolbarProvider } from "./contexts/toolbar-context";
|
|
8
|
+
import { SavedViewProvider } from "./saved-view-context";
|
|
9
|
+
import { DataViewer as DataViewerExplorer } from "./data-viewer";
|
|
6
10
|
|
|
7
11
|
/**
|
|
8
12
|
* Configuration for createDataViewer factory
|
|
@@ -10,8 +14,8 @@ import { ToolbarProvider } from "./contexts/toolbar-context";
|
|
|
10
14
|
export interface CreateDataViewerConfig<TMetadata extends TableMetadataMap> {
|
|
11
15
|
/** All table metadata (generated from TailorDB schema) */
|
|
12
16
|
metadata: TMetadata;
|
|
13
|
-
/**
|
|
14
|
-
|
|
17
|
+
/** GraphQL fetcher for data fetching */
|
|
18
|
+
fetcher: GraphQLFetcher;
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
/**
|
|
@@ -35,6 +39,20 @@ export interface DataViewerToolbarProviderProps {
|
|
|
35
39
|
children: ReactNode;
|
|
36
40
|
}
|
|
37
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Props for DataViewer.ExplorerView (tab-based UI)
|
|
44
|
+
*/
|
|
45
|
+
export interface ExplorerViewProps {
|
|
46
|
+
/**
|
|
47
|
+
* Saved view store for persisting views.
|
|
48
|
+
* Can be a store instance or a factory function that receives the fetcher.
|
|
49
|
+
* If not provided, views won't be persisted.
|
|
50
|
+
*/
|
|
51
|
+
savedViewStore?: SavedViewStore | SavedViewStoreFactory;
|
|
52
|
+
/** Initial view ID to load on mount */
|
|
53
|
+
initialViewId?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
38
56
|
/**
|
|
39
57
|
* Return type of createDataViewer factory
|
|
40
58
|
*/
|
|
@@ -53,29 +71,39 @@ export interface DataViewer<TMetadata extends TableMetadataMap> {
|
|
|
53
71
|
*/
|
|
54
72
|
ToolbarProvider: (props: DataViewerToolbarProviderProps) => ReactNode;
|
|
55
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Tab-based explorer view component - provides a full-featured data exploration UI
|
|
76
|
+
* with multiple tabs, table selection, and saved views support
|
|
77
|
+
*/
|
|
78
|
+
ExplorerView: (props: ExplorerViewProps) => ReactNode;
|
|
79
|
+
|
|
56
80
|
/**
|
|
57
81
|
* The metadata passed to createDataViewer
|
|
58
82
|
*/
|
|
59
83
|
metadata: TMetadata;
|
|
60
84
|
|
|
61
85
|
/**
|
|
62
|
-
* The
|
|
86
|
+
* The fetcher passed to createDataViewer
|
|
63
87
|
*/
|
|
64
|
-
|
|
88
|
+
fetcher: GraphQLFetcher;
|
|
65
89
|
}
|
|
66
90
|
|
|
67
91
|
/**
|
|
68
|
-
* Factory function to create a DataViewer instance with fixed metadata and
|
|
92
|
+
* Factory function to create a DataViewer instance with fixed metadata and fetcher
|
|
69
93
|
*
|
|
70
94
|
* @example
|
|
71
95
|
* ```tsx
|
|
72
96
|
* // src/lib/data-viewer.ts (create once per app)
|
|
73
|
-
* import { createDataViewer } from "@tailor-platform/data-viewer";
|
|
97
|
+
* import { createDataViewer, createDefaultFetcher } from "@tailor-platform/data-viewer";
|
|
74
98
|
* import { tableMetadata } from "./generated/metadata";
|
|
75
99
|
*
|
|
100
|
+
* const fetcher = createDefaultFetcher({
|
|
101
|
+
* endpoint: import.meta.env.VITE_APP_URL,
|
|
102
|
+
* });
|
|
103
|
+
*
|
|
76
104
|
* export const DataViewer = createDataViewer({
|
|
77
105
|
* metadata: tableMetadata,
|
|
78
|
-
*
|
|
106
|
+
* fetcher,
|
|
79
107
|
* });
|
|
80
108
|
*
|
|
81
109
|
* // pages/suppliers/page.tsx (use in each page)
|
|
@@ -99,9 +127,9 @@ export interface DataViewer<TMetadata extends TableMetadataMap> {
|
|
|
99
127
|
export function createDataViewer<TMetadata extends TableMetadataMap>(
|
|
100
128
|
config: CreateDataViewerConfig<TMetadata>,
|
|
101
129
|
): DataViewer<TMetadata> {
|
|
102
|
-
const { metadata,
|
|
130
|
+
const { metadata, fetcher } = config;
|
|
103
131
|
|
|
104
|
-
// TableDataProvider with fixed metadata and
|
|
132
|
+
// TableDataProvider with fixed metadata and fetcher
|
|
105
133
|
function TableDataProviderWrapper<
|
|
106
134
|
TTableName extends keyof TMetadata & string,
|
|
107
135
|
>({
|
|
@@ -112,7 +140,7 @@ export function createDataViewer<TMetadata extends TableMetadataMap>(
|
|
|
112
140
|
return (
|
|
113
141
|
<TableDataProvider
|
|
114
142
|
metadata={metadata}
|
|
115
|
-
|
|
143
|
+
fetcher={fetcher}
|
|
116
144
|
tableName={tableName}
|
|
117
145
|
initialData={initialData}
|
|
118
146
|
>
|
|
@@ -128,10 +156,41 @@ export function createDataViewer<TMetadata extends TableMetadataMap>(
|
|
|
128
156
|
return <ToolbarProvider>{children}</ToolbarProvider>;
|
|
129
157
|
}
|
|
130
158
|
|
|
159
|
+
// ExplorerView - tab-based UI with saved views support
|
|
160
|
+
function ExplorerViewWrapper({
|
|
161
|
+
savedViewStore,
|
|
162
|
+
initialViewId,
|
|
163
|
+
}: ExplorerViewProps): ReactNode {
|
|
164
|
+
// Resolve store (call factory if needed)
|
|
165
|
+
const store = savedViewStore
|
|
166
|
+
? typeof savedViewStore === "function"
|
|
167
|
+
? savedViewStore({ fetcher })
|
|
168
|
+
: savedViewStore
|
|
169
|
+
: undefined;
|
|
170
|
+
|
|
171
|
+
const explorerContent = (
|
|
172
|
+
<DataViewerExplorer
|
|
173
|
+
metadata={metadata}
|
|
174
|
+
fetcher={fetcher}
|
|
175
|
+
initialViewId={initialViewId}
|
|
176
|
+
/>
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
// Wrap with SavedViewProvider only if store is provided
|
|
180
|
+
if (store) {
|
|
181
|
+
return (
|
|
182
|
+
<SavedViewProvider store={store}>{explorerContent}</SavedViewProvider>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return explorerContent;
|
|
187
|
+
}
|
|
188
|
+
|
|
131
189
|
return {
|
|
132
190
|
TableDataProvider: TableDataProviderWrapper,
|
|
133
191
|
ToolbarProvider: ToolbarProviderWrapper,
|
|
192
|
+
ExplorerView: ExplorerViewWrapper,
|
|
134
193
|
metadata,
|
|
135
|
-
|
|
194
|
+
fetcher,
|
|
136
195
|
};
|
|
137
196
|
}
|