@vendure/dashboard 3.3.8-master-202507220240 → 3.3.8-master-202507240240
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/plugin/utils/plugin-discovery.d.ts +1 -1
- package/dist/plugin/utils/plugin-discovery.js +61 -21
- package/dist/plugin/vite-plugin-tailwind-source.js +13 -1
- package/dist/plugin/vite-plugin-vendure-dashboard.d.ts +1 -1
- package/dist/plugin/vite-plugin-vendure-dashboard.js +2 -2
- package/package.json +39 -26
- package/src/app/routes/_authenticated/_facets/components/add-facet-value-dialog.tsx +146 -0
- package/src/app/routes/_authenticated/_facets/components/facet-values-table.tsx +84 -62
- package/src/app/routes/_authenticated/_facets/facets.graphql.ts +9 -0
- package/src/lib/components/data-table/use-generated-columns.tsx +20 -5
- package/src/lib/components/ui/aspect-ratio.tsx +9 -0
- package/src/lib/components/ui/carousel.tsx +241 -0
- package/src/lib/components/ui/chart.tsx +351 -0
- package/src/lib/components/ui/context-menu.tsx +252 -0
- package/src/lib/components/ui/drawer.tsx +133 -0
- package/src/lib/components/ui/input-otp.tsx +77 -0
- package/src/lib/components/ui/menubar.tsx +274 -0
- package/src/lib/components/ui/navigation-menu.tsx +168 -0
- package/src/lib/components/ui/progress.tsx +29 -0
- package/src/lib/components/ui/radio-group.tsx +45 -0
- package/src/lib/components/ui/resizable.tsx +54 -0
- package/src/lib/components/ui/slider.tsx +63 -0
- package/src/lib/components/ui/toggle-group.tsx +73 -0
- package/src/lib/components/ui/toggle.tsx +45 -0
- package/src/lib/index.ts +18 -0
- package/vite/utils/plugin-discovery.ts +67 -18
- package/vite/vite-plugin-tailwind-source.ts +20 -4
- package/vite/vite-plugin-vendure-dashboard.ts +3 -3
|
@@ -12,7 +12,7 @@ export declare function discoverPlugins({ vendureConfigPath, transformTsConfigPa
|
|
|
12
12
|
* 1. Local Vendure plugins
|
|
13
13
|
* 2. All non-local package imports that could contain plugins
|
|
14
14
|
*/
|
|
15
|
-
export declare function analyzeSourceFiles(vendureConfigPath: string, logger: Logger, transformTsConfigPathMappings: TransformTsConfigPathMappingsFn): Promise<{
|
|
15
|
+
export declare function analyzeSourceFiles(vendureConfigPath: string, nodeModulesRoot: string, logger: Logger, transformTsConfigPathMappings: TransformTsConfigPathMappingsFn): Promise<{
|
|
16
16
|
localPluginLocations: Map<string, string>;
|
|
17
17
|
packageImports: string[];
|
|
18
18
|
}>;
|
|
@@ -8,9 +8,10 @@ import * as ts from 'typescript';
|
|
|
8
8
|
import { fileURLToPath } from 'url';
|
|
9
9
|
import { findTsConfigPaths } from './tsconfig-utils.js';
|
|
10
10
|
export async function discoverPlugins({ vendureConfigPath, transformTsConfigPathMappings, logger, outputPath, pluginPackageScanner, }) {
|
|
11
|
+
var _a;
|
|
11
12
|
const plugins = [];
|
|
12
13
|
// Analyze source files to find local plugins and package imports
|
|
13
|
-
const { localPluginLocations, packageImports } = await analyzeSourceFiles(vendureConfigPath, logger, transformTsConfigPathMappings);
|
|
14
|
+
const { localPluginLocations, packageImports } = await analyzeSourceFiles(vendureConfigPath, (_a = pluginPackageScanner === null || pluginPackageScanner === void 0 ? void 0 : pluginPackageScanner.nodeModulesRoot) !== null && _a !== void 0 ? _a : guessNodeModulesRoot(vendureConfigPath, logger), logger, transformTsConfigPathMappings);
|
|
14
15
|
logger.debug(`[discoverPlugins] Found ${localPluginLocations.size} local plugins: ${JSON.stringify([...localPluginLocations.entries()], null, 2)}`);
|
|
15
16
|
logger.debug(`[discoverPlugins] Found ${packageImports.length} package imports: ${JSON.stringify(packageImports, null, 2)}`);
|
|
16
17
|
const filePaths = await findVendurePluginFiles({
|
|
@@ -92,12 +93,32 @@ function getDecoratorObjectProps(decorator) {
|
|
|
92
93
|
}
|
|
93
94
|
return [];
|
|
94
95
|
}
|
|
96
|
+
async function isSymlinkedLocalPackage(packageName, nodeModulesRoot) {
|
|
97
|
+
try {
|
|
98
|
+
const packagePath = path.join(nodeModulesRoot, packageName);
|
|
99
|
+
const stats = await fs.lstat(packagePath);
|
|
100
|
+
if (stats.isSymbolicLink()) {
|
|
101
|
+
// Get the real path that the symlink points to
|
|
102
|
+
const realPath = await fs.realpath(packagePath);
|
|
103
|
+
// If the real path is within the project directory (i.e. not in some other node_modules),
|
|
104
|
+
// then it's a local package
|
|
105
|
+
if (!realPath.includes('node_modules')) {
|
|
106
|
+
return realPath;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch (e) {
|
|
111
|
+
// Package doesn't exist or other error - not a local package
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
95
116
|
/**
|
|
96
117
|
* Analyzes TypeScript source files starting from the config file to discover:
|
|
97
118
|
* 1. Local Vendure plugins
|
|
98
119
|
* 2. All non-local package imports that could contain plugins
|
|
99
120
|
*/
|
|
100
|
-
export async function analyzeSourceFiles(vendureConfigPath, logger, transformTsConfigPathMappings) {
|
|
121
|
+
export async function analyzeSourceFiles(vendureConfigPath, nodeModulesRoot, logger, transformTsConfigPathMappings) {
|
|
101
122
|
const localPluginLocations = new Map();
|
|
102
123
|
const visitedFiles = new Set();
|
|
103
124
|
const packageImportsSet = new Set();
|
|
@@ -123,7 +144,7 @@ export async function analyzeSourceFiles(vendureConfigPath, logger, transformTsC
|
|
|
123
144
|
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
124
145
|
// Track imports to follow
|
|
125
146
|
const importsToFollow = [];
|
|
126
|
-
function visit(node) {
|
|
147
|
+
async function visit(node) {
|
|
127
148
|
// Look for VendurePlugin decorator
|
|
128
149
|
const vendurePluginClassName = getVendurePluginClassName(node);
|
|
129
150
|
if (vendurePluginClassName) {
|
|
@@ -139,7 +160,16 @@ export async function analyzeSourceFiles(vendureConfigPath, logger, transformTsC
|
|
|
139
160
|
// Track non-local imports (packages)
|
|
140
161
|
const npmPackageName = getNpmPackageNameFromImport(importPath);
|
|
141
162
|
if (npmPackageName) {
|
|
142
|
-
|
|
163
|
+
// Check if this is actually a symlinked local package
|
|
164
|
+
const localPackagePath = await isSymlinkedLocalPackage(npmPackageName, nodeModulesRoot);
|
|
165
|
+
if (localPackagePath) {
|
|
166
|
+
// If it is local, follow it like a local import
|
|
167
|
+
importsToFollow.push(localPackagePath);
|
|
168
|
+
logger.debug(`Found symlinked local package "${npmPackageName}" at ${localPackagePath}`);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
packageImportsSet.add(npmPackageName);
|
|
172
|
+
}
|
|
143
173
|
}
|
|
144
174
|
// Handle path aliases and local imports
|
|
145
175
|
const pathAliasImports = getPotentialPathAliasImportPaths(importPath, tsConfigInfo);
|
|
@@ -153,9 +183,14 @@ export async function analyzeSourceFiles(vendureConfigPath, logger, transformTsC
|
|
|
153
183
|
}
|
|
154
184
|
}
|
|
155
185
|
}
|
|
156
|
-
|
|
186
|
+
// Visit children
|
|
187
|
+
const promises = [];
|
|
188
|
+
ts.forEachChild(node, child => {
|
|
189
|
+
promises.push(visit(child));
|
|
190
|
+
});
|
|
191
|
+
await Promise.all(promises);
|
|
157
192
|
}
|
|
158
|
-
visit(sourceFile);
|
|
193
|
+
await visit(sourceFile);
|
|
159
194
|
// Follow imports
|
|
160
195
|
for (const importPath of importsToFollow) {
|
|
161
196
|
// Try all possible file paths
|
|
@@ -265,27 +300,14 @@ export async function findVendurePluginFiles({ outputPath, vendureConfigPath, lo
|
|
|
265
300
|
let nodeModulesRoot = providedNodeModulesRoot;
|
|
266
301
|
const readStart = Date.now();
|
|
267
302
|
if (!nodeModulesRoot) {
|
|
268
|
-
|
|
269
|
-
// specified, we will try to guess it by resolving the
|
|
270
|
-
// `@vendure/core` package.
|
|
271
|
-
try {
|
|
272
|
-
const coreUrl = import.meta.resolve('@vendure/core');
|
|
273
|
-
logger.debug(`Found core URL: ${coreUrl}`);
|
|
274
|
-
const corePath = fileURLToPath(coreUrl);
|
|
275
|
-
logger.debug(`Found core path: ${corePath}`);
|
|
276
|
-
nodeModulesRoot = path.join(path.dirname(corePath), '..', '..');
|
|
277
|
-
}
|
|
278
|
-
catch (e) {
|
|
279
|
-
logger.warn(`Failed to resolve @vendure/core: ${e instanceof Error ? e.message : String(e)}`);
|
|
280
|
-
nodeModulesRoot = path.dirname(vendureConfigPath);
|
|
281
|
-
}
|
|
303
|
+
nodeModulesRoot = guessNodeModulesRoot(vendureConfigPath, logger);
|
|
282
304
|
}
|
|
283
305
|
const patterns = [
|
|
284
306
|
// Local compiled plugins in temp dir
|
|
285
307
|
path.join(outputPath, '**/*.js'),
|
|
286
308
|
// Node modules patterns
|
|
287
309
|
...packageGlobs.map(pattern => path.join(nodeModulesRoot, pattern)),
|
|
288
|
-
];
|
|
310
|
+
].map(p => p.replace(/\\/g, '/'));
|
|
289
311
|
logger.debug(`Finding Vendure plugins using patterns: ${patterns.join('\n')}`);
|
|
290
312
|
const globStart = Date.now();
|
|
291
313
|
const files = await glob(patterns, {
|
|
@@ -345,3 +367,21 @@ export async function findVendurePluginFiles({ outputPath, vendureConfigPath, lo
|
|
|
345
367
|
`(scanned ${files.length} files)`);
|
|
346
368
|
return potentialPluginFiles;
|
|
347
369
|
}
|
|
370
|
+
function guessNodeModulesRoot(vendureConfigPath, logger) {
|
|
371
|
+
let nodeModulesRoot;
|
|
372
|
+
// If the node_modules root path has not been explicitly
|
|
373
|
+
// specified, we will try to guess it by resolving the
|
|
374
|
+
// `@vendure/core` package.
|
|
375
|
+
try {
|
|
376
|
+
const coreUrl = import.meta.resolve('@vendure/core');
|
|
377
|
+
logger.debug(`Found core URL: ${coreUrl}`);
|
|
378
|
+
const corePath = fileURLToPath(coreUrl);
|
|
379
|
+
logger.debug(`Found core path: ${corePath}`);
|
|
380
|
+
nodeModulesRoot = path.join(path.dirname(corePath), '..', '..');
|
|
381
|
+
}
|
|
382
|
+
catch (e) {
|
|
383
|
+
logger.warn(`Failed to resolve @vendure/core: ${e instanceof Error ? e.message : String(e)}`);
|
|
384
|
+
nodeModulesRoot = path.dirname(vendureConfigPath);
|
|
385
|
+
}
|
|
386
|
+
return nodeModulesRoot;
|
|
387
|
+
}
|
|
@@ -22,7 +22,19 @@ export function dashboardTailwindSourcePlugin() {
|
|
|
22
22
|
loadVendureConfigResult = await configLoaderApi.getVendureConfig();
|
|
23
23
|
}
|
|
24
24
|
const { pluginInfo } = loadVendureConfigResult;
|
|
25
|
-
const dashboardExtensionDirs = (_a = pluginInfo === null || pluginInfo === void 0 ? void 0 : pluginInfo.
|
|
25
|
+
const dashboardExtensionDirs = (_a = pluginInfo === null || pluginInfo === void 0 ? void 0 : pluginInfo.flatMap(({ dashboardEntryPath, sourcePluginPath, pluginPath }) => {
|
|
26
|
+
if (!dashboardEntryPath) {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
const sourcePaths = [];
|
|
30
|
+
if (sourcePluginPath) {
|
|
31
|
+
sourcePaths.push(path.join(path.dirname(sourcePluginPath), path.dirname(dashboardEntryPath)));
|
|
32
|
+
}
|
|
33
|
+
if (pluginPath) {
|
|
34
|
+
sourcePaths.push(path.join(path.dirname(pluginPath), path.dirname(dashboardEntryPath)));
|
|
35
|
+
}
|
|
36
|
+
return sourcePaths;
|
|
37
|
+
}).filter(x => x != null)) !== null && _a !== void 0 ? _a : [];
|
|
26
38
|
const sources = dashboardExtensionDirs
|
|
27
39
|
.map(extension => {
|
|
28
40
|
return `@source '${extension}';`;
|
|
@@ -63,7 +63,7 @@ export type VitePluginVendureDashboardOptions = {
|
|
|
63
63
|
* @description
|
|
64
64
|
* The path to the directory where the generated GraphQL Tada files will be output.
|
|
65
65
|
*/
|
|
66
|
-
|
|
66
|
+
gqlOutputPath?: string;
|
|
67
67
|
tempCompilationDir?: string;
|
|
68
68
|
disableTansStackRouterPlugin?: boolean;
|
|
69
69
|
/**
|
|
@@ -55,8 +55,8 @@ export function vendureDashboardPlugin(options) {
|
|
|
55
55
|
adminApiSchemaPlugin(),
|
|
56
56
|
dashboardMetadataPlugin(),
|
|
57
57
|
uiConfigPlugin({ adminUiConfig: options.adminUiConfig }),
|
|
58
|
-
...(options.
|
|
59
|
-
? [gqlTadaPlugin({ gqlTadaOutputPath: options.
|
|
58
|
+
...(options.gqlOutputPath
|
|
59
|
+
? [gqlTadaPlugin({ gqlTadaOutputPath: options.gqlOutputPath, tempDir, packageRoot })]
|
|
60
60
|
: []),
|
|
61
61
|
transformIndexHtmlPlugin(),
|
|
62
62
|
];
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vendure/dashboard",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "3.3.8-master-
|
|
4
|
+
"version": "3.3.8-master-202507240240",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -54,23 +54,32 @@
|
|
|
54
54
|
"@lingui/core": "^5.2.0",
|
|
55
55
|
"@lingui/react": "^5.2.0",
|
|
56
56
|
"@lingui/vite-plugin": "^5.2.0",
|
|
57
|
-
"@radix-ui/react-accordion": "^1.2.
|
|
58
|
-
"@radix-ui/react-alert-dialog": "^1.1.
|
|
59
|
-
"@radix-ui/react-
|
|
60
|
-
"@radix-ui/react-
|
|
61
|
-
"@radix-ui/react-
|
|
62
|
-
"@radix-ui/react-
|
|
63
|
-
"@radix-ui/react-
|
|
64
|
-
"@radix-ui/react-
|
|
65
|
-
"@radix-ui/react-
|
|
66
|
-
"@radix-ui/react-
|
|
67
|
-
"@radix-ui/react-
|
|
57
|
+
"@radix-ui/react-accordion": "^1.2.11",
|
|
58
|
+
"@radix-ui/react-alert-dialog": "^1.1.14",
|
|
59
|
+
"@radix-ui/react-aspect-ratio": "^1.1.7",
|
|
60
|
+
"@radix-ui/react-avatar": "^1.1.10",
|
|
61
|
+
"@radix-ui/react-checkbox": "^1.3.2",
|
|
62
|
+
"@radix-ui/react-collapsible": "^1.1.11",
|
|
63
|
+
"@radix-ui/react-context-menu": "^2.2.15",
|
|
64
|
+
"@radix-ui/react-dialog": "^1.1.14",
|
|
65
|
+
"@radix-ui/react-dropdown-menu": "^2.1.15",
|
|
66
|
+
"@radix-ui/react-hover-card": "^1.1.14",
|
|
67
|
+
"@radix-ui/react-label": "^2.1.7",
|
|
68
|
+
"@radix-ui/react-menubar": "^1.1.15",
|
|
69
|
+
"@radix-ui/react-navigation-menu": "^1.2.13",
|
|
70
|
+
"@radix-ui/react-popover": "^1.1.14",
|
|
71
|
+
"@radix-ui/react-progress": "^1.1.7",
|
|
72
|
+
"@radix-ui/react-radio-group": "^1.3.7",
|
|
73
|
+
"@radix-ui/react-scroll-area": "^1.2.9",
|
|
68
74
|
"@radix-ui/react-select": "^2.2.5",
|
|
69
|
-
"@radix-ui/react-separator": "^1.1.
|
|
70
|
-
"@radix-ui/react-
|
|
71
|
-
"@radix-ui/react-
|
|
72
|
-
"@radix-ui/react-
|
|
73
|
-
"@radix-ui/react-
|
|
75
|
+
"@radix-ui/react-separator": "^1.1.7",
|
|
76
|
+
"@radix-ui/react-slider": "^1.3.5",
|
|
77
|
+
"@radix-ui/react-slot": "^1.2.3",
|
|
78
|
+
"@radix-ui/react-switch": "^1.2.5",
|
|
79
|
+
"@radix-ui/react-tabs": "^1.1.12",
|
|
80
|
+
"@radix-ui/react-toggle": "^1.1.9",
|
|
81
|
+
"@radix-ui/react-toggle-group": "^1.1.10",
|
|
82
|
+
"@radix-ui/react-tooltip": "^1.2.7",
|
|
74
83
|
"@tailwindcss/vite": "^4.1.5",
|
|
75
84
|
"@tanstack/eslint-plugin-query": "^5.66.1",
|
|
76
85
|
"@tanstack/react-query": "^5.66.7",
|
|
@@ -86,38 +95,42 @@
|
|
|
86
95
|
"@types/react-dom": "^19.0.4",
|
|
87
96
|
"@types/react-grid-layout": "^1.3.5",
|
|
88
97
|
"@uidotdev/usehooks": "^2.4.1",
|
|
89
|
-
"@vendure/common": "^3.3.8-master-
|
|
90
|
-
"@vendure/core": "^3.3.8-master-
|
|
98
|
+
"@vendure/common": "^3.3.8-master-202507240240",
|
|
99
|
+
"@vendure/core": "^3.3.8-master-202507240240",
|
|
91
100
|
"@vitejs/plugin-react": "^4.3.4",
|
|
92
101
|
"acorn": "^8.11.3",
|
|
93
102
|
"acorn-walk": "^8.3.2",
|
|
94
103
|
"awesome-graphql-client": "^2.1.0",
|
|
95
104
|
"class-variance-authority": "^0.7.1",
|
|
96
105
|
"clsx": "^2.1.1",
|
|
97
|
-
"cmdk": "^1.
|
|
106
|
+
"cmdk": "^1.1.1",
|
|
98
107
|
"date-fns": "^3.6.0",
|
|
108
|
+
"embla-carousel-react": "^8.6.0",
|
|
99
109
|
"fast-glob": "^3.3.2",
|
|
100
110
|
"gql.tada": "^1.8.10",
|
|
101
111
|
"graphql": "^16.10.0",
|
|
112
|
+
"input-otp": "^1.4.2",
|
|
102
113
|
"json-edit-react": "^1.23.1",
|
|
103
114
|
"lucide-react": "^0.475.0",
|
|
104
115
|
"motion": "^12.6.2",
|
|
105
116
|
"next-themes": "^0.4.6",
|
|
106
117
|
"react": "^19.0.0",
|
|
107
|
-
"react-day-picker": "^9.
|
|
118
|
+
"react-day-picker": "^9.8.0",
|
|
108
119
|
"react-dom": "^19.0.0",
|
|
109
120
|
"react-dropzone": "^14.3.8",
|
|
110
121
|
"react-grid-layout": "^1.5.1",
|
|
111
|
-
"react-hook-form": "^7.
|
|
112
|
-
"
|
|
113
|
-
"
|
|
122
|
+
"react-hook-form": "^7.60.0",
|
|
123
|
+
"react-resizable-panels": "^3.0.3",
|
|
124
|
+
"recharts": "^2.15.4",
|
|
125
|
+
"sonner": "^2.0.6",
|
|
114
126
|
"tailwind-merge": "^3.2.0",
|
|
115
127
|
"tailwindcss": "^4.1.5",
|
|
116
128
|
"tailwindcss-animate": "^1.0.7",
|
|
117
129
|
"tsconfig-paths": "^4.2.0",
|
|
118
130
|
"tw-animate-css": "^1.2.9",
|
|
131
|
+
"vaul": "^1.1.2",
|
|
119
132
|
"vite": "^6.3.5",
|
|
120
|
-
"zod": "^3.
|
|
133
|
+
"zod": "^3.25.76"
|
|
121
134
|
},
|
|
122
135
|
"devDependencies": {
|
|
123
136
|
"@eslint/js": "^9.19.0",
|
|
@@ -133,5 +146,5 @@
|
|
|
133
146
|
"lightningcss-linux-arm64-musl": "^1.29.3",
|
|
134
147
|
"lightningcss-linux-x64-musl": "^1.29.1"
|
|
135
148
|
},
|
|
136
|
-
"gitHead": "
|
|
149
|
+
"gitHead": "5a2448f30b88b5c0fd038adc2966a1147c9a0406"
|
|
137
150
|
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { FormFieldWrapper } from '@/vdb/components/shared/form-field-wrapper.js';
|
|
2
|
+
import { Button } from '@/vdb/components/ui/button.js';
|
|
3
|
+
import {
|
|
4
|
+
Dialog,
|
|
5
|
+
DialogContent,
|
|
6
|
+
DialogFooter,
|
|
7
|
+
DialogHeader,
|
|
8
|
+
DialogTitle,
|
|
9
|
+
DialogTrigger,
|
|
10
|
+
} from '@/vdb/components/ui/dialog.js';
|
|
11
|
+
import { Form } from '@/vdb/components/ui/form.js';
|
|
12
|
+
import { Input } from '@/vdb/components/ui/input.js';
|
|
13
|
+
import { api } from '@/vdb/graphql/api.js';
|
|
14
|
+
import { graphql } from '@/vdb/graphql/graphql.js';
|
|
15
|
+
import { Trans, useLingui } from '@/vdb/lib/trans.js';
|
|
16
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
17
|
+
import { useMutation } from '@tanstack/react-query';
|
|
18
|
+
import { Plus } from 'lucide-react';
|
|
19
|
+
import { useCallback, useState } from 'react';
|
|
20
|
+
import { useForm } from 'react-hook-form';
|
|
21
|
+
import { toast } from 'sonner';
|
|
22
|
+
import * as z from 'zod';
|
|
23
|
+
|
|
24
|
+
const createFacetValuesDocument = graphql(`
|
|
25
|
+
mutation CreateFacetValues($input: [CreateFacetValueInput!]!) {
|
|
26
|
+
createFacetValues(input: $input) {
|
|
27
|
+
id
|
|
28
|
+
name
|
|
29
|
+
code
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
`);
|
|
33
|
+
|
|
34
|
+
const formSchema = z.object({
|
|
35
|
+
name: z.string().min(1, 'Name is required'),
|
|
36
|
+
code: z.string().min(1, 'Code is required'),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
type FormValues = z.infer<typeof formSchema>;
|
|
40
|
+
|
|
41
|
+
export function AddFacetValueDialog({
|
|
42
|
+
facetId,
|
|
43
|
+
onSuccess,
|
|
44
|
+
}: Readonly<{
|
|
45
|
+
facetId: string;
|
|
46
|
+
onSuccess?: () => void;
|
|
47
|
+
}>) {
|
|
48
|
+
const [open, setOpen] = useState(false);
|
|
49
|
+
const { i18n } = useLingui();
|
|
50
|
+
|
|
51
|
+
const form = useForm<FormValues>({
|
|
52
|
+
resolver: zodResolver(formSchema),
|
|
53
|
+
defaultValues: {
|
|
54
|
+
name: '',
|
|
55
|
+
code: '',
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const createFacetValueMutation = useMutation({
|
|
60
|
+
mutationFn: api.mutate(createFacetValuesDocument),
|
|
61
|
+
onSuccess: () => {
|
|
62
|
+
toast.success(i18n.t('Successfully created facet value'));
|
|
63
|
+
setOpen(false);
|
|
64
|
+
form.reset();
|
|
65
|
+
onSuccess?.();
|
|
66
|
+
},
|
|
67
|
+
onError: error => {
|
|
68
|
+
toast.error(i18n.t('Failed to create facet value'), {
|
|
69
|
+
description: error instanceof Error ? error.message : i18n.t('Unknown error'),
|
|
70
|
+
});
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const onSubmit = useCallback(
|
|
75
|
+
(values: FormValues) => {
|
|
76
|
+
createFacetValueMutation.mutate({
|
|
77
|
+
input: [
|
|
78
|
+
{
|
|
79
|
+
facetId,
|
|
80
|
+
code: values.code,
|
|
81
|
+
translations: [
|
|
82
|
+
{
|
|
83
|
+
languageCode: 'en',
|
|
84
|
+
name: values.name,
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
});
|
|
90
|
+
},
|
|
91
|
+
[createFacetValueMutation, facetId],
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<Dialog open={open} onOpenChange={setOpen}>
|
|
96
|
+
<DialogTrigger asChild>
|
|
97
|
+
<Button variant="outline">
|
|
98
|
+
<Plus className="mr-2 h-4 w-4" />
|
|
99
|
+
<Trans>Add facet value</Trans>
|
|
100
|
+
</Button>
|
|
101
|
+
</DialogTrigger>
|
|
102
|
+
<DialogContent>
|
|
103
|
+
<DialogHeader>
|
|
104
|
+
<DialogTitle>
|
|
105
|
+
<Trans>Add facet value</Trans>
|
|
106
|
+
</DialogTitle>
|
|
107
|
+
</DialogHeader>
|
|
108
|
+
<Form {...form}>
|
|
109
|
+
<form
|
|
110
|
+
onSubmit={e => {
|
|
111
|
+
e.stopPropagation();
|
|
112
|
+
form.handleSubmit(onSubmit)(e);
|
|
113
|
+
}}
|
|
114
|
+
className="space-y-4"
|
|
115
|
+
>
|
|
116
|
+
<FormFieldWrapper
|
|
117
|
+
control={form.control}
|
|
118
|
+
name="name"
|
|
119
|
+
label={<Trans>Name</Trans>}
|
|
120
|
+
render={({ field }) => <Input {...field} />}
|
|
121
|
+
/>
|
|
122
|
+
<FormFieldWrapper
|
|
123
|
+
control={form.control}
|
|
124
|
+
name="code"
|
|
125
|
+
label={<Trans>Code</Trans>}
|
|
126
|
+
render={({ field }) => <Input {...field} />}
|
|
127
|
+
/>
|
|
128
|
+
<DialogFooter>
|
|
129
|
+
<Button
|
|
130
|
+
type="submit"
|
|
131
|
+
disabled={
|
|
132
|
+
createFacetValueMutation.isPending ||
|
|
133
|
+
!form.formState.isValid ||
|
|
134
|
+
!form.watch('name').trim() ||
|
|
135
|
+
!form.watch('code').trim()
|
|
136
|
+
}
|
|
137
|
+
>
|
|
138
|
+
<Trans>Create facet value</Trans>
|
|
139
|
+
</Button>
|
|
140
|
+
</DialogFooter>
|
|
141
|
+
</form>
|
|
142
|
+
</Form>
|
|
143
|
+
</DialogContent>
|
|
144
|
+
</Dialog>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
@@ -5,8 +5,10 @@ import { addCustomFields } from '@/vdb/framework/document-introspection/add-cust
|
|
|
5
5
|
import { graphql } from '@/vdb/graphql/graphql.js';
|
|
6
6
|
import { Trans } from '@/vdb/lib/trans.js';
|
|
7
7
|
import { ColumnFiltersState, SortingState } from '@tanstack/react-table';
|
|
8
|
-
import { useState } from 'react';
|
|
8
|
+
import { useRef, useState } from 'react';
|
|
9
|
+
import { AddFacetValueDialog } from './add-facet-value-dialog.js';
|
|
9
10
|
import { EditFacetValue } from './edit-facet-value.js';
|
|
11
|
+
import { deleteFacetValuesDocument } from '../facets.graphql.js';
|
|
10
12
|
|
|
11
13
|
export const facetValueListDocument = graphql(`
|
|
12
14
|
query FacetValueList($options: FacetValueListOptions) {
|
|
@@ -26,76 +28,96 @@ export const facetValueListDocument = graphql(`
|
|
|
26
28
|
|
|
27
29
|
export interface FacetValuesTableProps {
|
|
28
30
|
facetId: string;
|
|
31
|
+
registerRefresher?: (refresher: () => void) => void;
|
|
29
32
|
}
|
|
30
33
|
|
|
31
|
-
export function FacetValuesTable({ facetId }: Readonly<FacetValuesTableProps>) {
|
|
34
|
+
export function FacetValuesTable({ facetId, registerRefresher }: Readonly<FacetValuesTableProps>) {
|
|
32
35
|
const [sorting, setSorting] = useState<SortingState>([]);
|
|
33
36
|
const [page, setPage] = useState(1);
|
|
34
37
|
const [pageSize, setPageSize] = useState(10);
|
|
35
38
|
const [filters, setFilters] = useState<ColumnFiltersState>([]);
|
|
39
|
+
const refreshRef = useRef<() => void>(() => {});
|
|
36
40
|
|
|
37
41
|
return (
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
42
|
+
<>
|
|
43
|
+
<PaginatedListDataTable
|
|
44
|
+
listQuery={addCustomFields(facetValueListDocument)}
|
|
45
|
+
deleteMutation={deleteFacetValuesDocument}
|
|
46
|
+
page={page}
|
|
47
|
+
itemsPerPage={pageSize}
|
|
48
|
+
sorting={sorting}
|
|
49
|
+
columnFilters={filters}
|
|
50
|
+
onPageChange={(_, page, perPage) => {
|
|
51
|
+
setPage(page);
|
|
52
|
+
setPageSize(perPage);
|
|
53
|
+
}}
|
|
54
|
+
onSortChange={(_, sorting) => {
|
|
55
|
+
setSorting(sorting);
|
|
56
|
+
}}
|
|
57
|
+
onFilterChange={(_, filters) => {
|
|
58
|
+
setFilters(filters);
|
|
59
|
+
}}
|
|
60
|
+
registerRefresher={refresher => {
|
|
61
|
+
refreshRef.current = refresher;
|
|
62
|
+
registerRefresher?.(refresher);
|
|
63
|
+
}}
|
|
64
|
+
transformVariables={variables => {
|
|
65
|
+
const filter = variables.options?.filter ?? {};
|
|
66
|
+
return {
|
|
67
|
+
options: {
|
|
68
|
+
filter: {
|
|
69
|
+
...filter,
|
|
70
|
+
facetId: { eq: facetId },
|
|
71
|
+
},
|
|
72
|
+
sort: variables.options?.sort,
|
|
73
|
+
take: pageSize,
|
|
74
|
+
skip: (page - 1) * pageSize,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}}
|
|
78
|
+
onSearchTermChange={searchTerm => {
|
|
79
|
+
return {
|
|
80
|
+
name: {
|
|
81
|
+
contains: searchTerm,
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}}
|
|
85
|
+
additionalColumns={{
|
|
86
|
+
actions: {
|
|
87
|
+
header: 'Actions',
|
|
88
|
+
cell: ({ row }) => {
|
|
89
|
+
const [open, setOpen] = useState(false);
|
|
90
|
+
const facetValue = row.original;
|
|
91
|
+
return (
|
|
92
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
93
|
+
<PopoverTrigger asChild>
|
|
94
|
+
<Button type="button" variant="outline" size="sm">
|
|
95
|
+
<Trans>Edit</Trans>
|
|
96
|
+
</Button>
|
|
97
|
+
</PopoverTrigger>
|
|
98
|
+
<PopoverContent className="w-80">
|
|
99
|
+
<EditFacetValue
|
|
100
|
+
facetValueId={facetValue.id}
|
|
101
|
+
onSuccess={() => {
|
|
102
|
+
setOpen(false);
|
|
103
|
+
refreshRef.current?.();
|
|
104
|
+
}}
|
|
105
|
+
/>
|
|
106
|
+
</PopoverContent>
|
|
107
|
+
</Popover>
|
|
108
|
+
);
|
|
61
109
|
},
|
|
62
|
-
sort: variables.options?.sort,
|
|
63
|
-
take: pageSize,
|
|
64
|
-
skip: (page - 1) * pageSize,
|
|
65
|
-
},
|
|
66
|
-
};
|
|
67
|
-
}}
|
|
68
|
-
onSearchTermChange={searchTerm => {
|
|
69
|
-
return {
|
|
70
|
-
name: {
|
|
71
|
-
contains: searchTerm,
|
|
72
|
-
},
|
|
73
|
-
};
|
|
74
|
-
}}
|
|
75
|
-
additionalColumns={{
|
|
76
|
-
actions: {
|
|
77
|
-
header: 'Actions',
|
|
78
|
-
cell: ({ row }) => {
|
|
79
|
-
const [open, setOpen] = useState(false);
|
|
80
|
-
const facetValue = row.original;
|
|
81
|
-
return (
|
|
82
|
-
<Popover open={open} onOpenChange={setOpen}>
|
|
83
|
-
<PopoverTrigger asChild>
|
|
84
|
-
<Button type="button" variant="outline" size="sm">
|
|
85
|
-
<Trans>Edit</Trans>
|
|
86
|
-
</Button>
|
|
87
|
-
</PopoverTrigger>
|
|
88
|
-
<PopoverContent className="w-80">
|
|
89
|
-
<EditFacetValue
|
|
90
|
-
facetValueId={facetValue.id}
|
|
91
|
-
onSuccess={() => setOpen(false)}
|
|
92
|
-
/>
|
|
93
|
-
</PopoverContent>
|
|
94
|
-
</Popover>
|
|
95
|
-
);
|
|
96
110
|
},
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
|
|
111
|
+
}}
|
|
112
|
+
/>
|
|
113
|
+
<div className="mt-4">
|
|
114
|
+
<AddFacetValueDialog
|
|
115
|
+
facetId={facetId}
|
|
116
|
+
onSuccess={() => {
|
|
117
|
+
refreshRef.current?.();
|
|
118
|
+
}}
|
|
119
|
+
/>
|
|
120
|
+
</div>
|
|
121
|
+
</>
|
|
100
122
|
);
|
|
101
123
|
}
|
|
@@ -132,3 +132,12 @@ export const deleteFacetsDocument = graphql(`
|
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
134
|
`);
|
|
135
|
+
|
|
136
|
+
export const deleteFacetValuesDocument = graphql(`
|
|
137
|
+
mutation DeleteFacetValues($ids: [ID!]!) {
|
|
138
|
+
deleteFacetValues(ids: $ids) {
|
|
139
|
+
result
|
|
140
|
+
message
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
`);
|