@vendure/dashboard 3.5.3-master-202601290259 → 3.5.3
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/vite/utils/plugin-discovery.js +3 -3
- package/dist/vite/utils/ui-config.js +15 -1
- package/dist/vite/vite-plugin-lingui-babel.d.ts +15 -2
- package/dist/vite/vite-plugin-lingui-babel.js +90 -8
- package/dist/vite/vite-plugin-translations.js +2 -2
- package/dist/vite/vite-plugin-ui-config.d.ts +31 -0
- package/package.json +3 -3
- package/src/app/routes/_authenticated/_collections/collections_.$id.tsx +22 -3
- package/src/app/routes/_authenticated/_customers/customers.graphql.ts +1 -0
- package/src/app/routes/_authenticated/_customers/customers.tsx +3 -0
- package/src/app/routes/_authenticated/_orders/components/draft-order-status.tsx +48 -0
- package/src/app/routes/_authenticated/_orders/components/fulfill-order-dialog.tsx +4 -4
- package/src/app/routes/_authenticated/_orders/components/order-detail-shared.tsx +60 -33
- package/src/app/routes/_authenticated/_orders/components/order-history/default-order-history-components.tsx +43 -3
- package/src/app/routes/_authenticated/_orders/components/order-history/order-history-utils.tsx +19 -3
- package/src/app/routes/_authenticated/_orders/components/order-table.tsx +1 -0
- package/src/app/routes/_authenticated/_orders/components/refund-order-dialog.tsx +372 -0
- package/src/app/routes/_authenticated/_orders/hooks/use-refund-order.ts +345 -0
- package/src/app/routes/_authenticated/_orders/orders.graphql.ts +41 -0
- package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +22 -6
- package/src/app/routes/_authenticated/_orders/utils/order-utils.ts +51 -0
- package/src/app/routes/_authenticated/_orders/utils/refund-utils.ts +100 -0
- package/src/app/routes/_authenticated/_orders/utils/use-modify-order.ts +1 -1
- package/src/app/routes/_authenticated/_payment-methods/payment-methods_.$id.tsx +1 -0
- package/src/app/routes/_authenticated/_product-variants/product-variants.graphql.ts +9 -3
- package/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx +49 -30
- package/src/app/routes/_authenticated/_profile/profile.graphql.ts +7 -0
- package/src/app/routes/_authenticated/_profile/profile.tsx +25 -1
- package/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx +1 -0
- package/src/app/routes/_authenticated/_shipping-methods/shipping-methods_.$id.tsx +1 -0
- package/src/i18n/common-strings.ts +7 -0
- package/src/i18n/locales/ar.po +915 -663
- package/src/i18n/locales/bg.po +1818 -46
- package/src/i18n/locales/cs.po +865 -666
- package/src/i18n/locales/de.po +865 -666
- package/src/i18n/locales/en.po +914 -662
- package/src/i18n/locales/es.po +865 -666
- package/src/i18n/locales/fa.po +865 -666
- package/src/i18n/locales/fr.po +865 -666
- package/src/i18n/locales/he.po +865 -666
- package/src/i18n/locales/hr.po +865 -666
- package/src/i18n/locales/it.po +865 -666
- package/src/i18n/locales/ja.po +865 -666
- package/src/i18n/locales/nb.po +865 -666
- package/src/i18n/locales/ne.po +865 -666
- package/src/i18n/locales/pl.po +865 -666
- package/src/i18n/locales/pt_BR.po +865 -666
- package/src/i18n/locales/pt_PT.po +865 -666
- package/src/i18n/locales/ru.po +865 -666
- package/src/i18n/locales/sv.po +865 -666
- package/src/i18n/locales/tr.po +865 -666
- package/src/i18n/locales/uk.po +865 -666
- package/src/i18n/locales/zh_Hans.po +865 -666
- package/src/i18n/locales/zh_Hant.po +865 -666
- package/src/lib/components/data-input/index.ts +1 -0
- package/src/lib/components/data-table/use-generated-columns.tsx +9 -2
- package/src/lib/components/shared/paginated-list-data-table.tsx +6 -2
- package/src/lib/components/ui/alert.tsx +2 -0
- package/src/lib/framework/extension-api/input-component-extensions.tsx +2 -0
- package/src/lib/framework/form-engine/form-schema-tools.ts +4 -1
- package/src/lib/framework/page/detail-page-route-loader.tsx +6 -4
- package/src/lib/framework/page/detail-page.tsx +22 -37
- package/src/lib/framework/page/list-page.stories.tsx +41 -2
- package/src/lib/framework/page/list-page.tsx +8 -0
- package/src/lib/graphql/graphql-env.d.ts +30 -13
- package/src/lib/hooks/use-dynamic-translations.ts +7 -0
- package/src/lib/hooks/use-job-queue-polling.ts +160 -0
- package/src/lib/virtual.d.ts +5 -0
|
@@ -323,9 +323,9 @@ export async function findVendurePluginFiles({ outputPath, vendureConfigPath, lo
|
|
|
323
323
|
const globStart = Date.now();
|
|
324
324
|
const files = await glob(patterns, {
|
|
325
325
|
ignore: [
|
|
326
|
-
// Skip nested node_modules (transitive deps) but not .pnpm
|
|
327
|
-
// [!.]
|
|
328
|
-
'**/node_modules/[!.
|
|
326
|
+
// Skip nested node_modules (transitive deps) but not .pnpm or .bun directories.
|
|
327
|
+
// [!.] excludes paths starting with . since pnpm and bun store packages there.
|
|
328
|
+
'**/node_modules/[!.]*/**/node_modules/**',
|
|
329
329
|
'**/*.spec.js',
|
|
330
330
|
'**/*.test.js',
|
|
331
331
|
],
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ADMIN_API_PATH, DEFAULT_AUTH_TOKEN_HEADER_KEY, DEFAULT_CHANNEL_TOKEN_KEY, } from '@vendure/common/lib/shared-constants';
|
|
2
2
|
import { defaultAvailableLanguages, defaultAvailableLocales, defaultLanguage, defaultLocale, } from '../constants.js';
|
|
3
3
|
export function getUiConfig(config, pluginOptions) {
|
|
4
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w;
|
|
4
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x;
|
|
5
5
|
const { authOptions, apiOptions } = config;
|
|
6
6
|
// Merge API configuration with defaults
|
|
7
7
|
const api = {
|
|
@@ -23,8 +23,22 @@ export function getUiConfig(config, pluginOptions) {
|
|
|
23
23
|
? pluginOptions.i18n.availableLocales
|
|
24
24
|
: defaultAvailableLocales,
|
|
25
25
|
};
|
|
26
|
+
// Merge orders configuration with defaults
|
|
27
|
+
// Default labels are identifiers that get translated via getTranslatedRefundReason()
|
|
28
|
+
const orders = {
|
|
29
|
+
refundReasons: ((_x = pluginOptions.orders) === null || _x === void 0 ? void 0 : _x.refundReasons) && pluginOptions.orders.refundReasons.length > 0
|
|
30
|
+
? pluginOptions.orders.refundReasons
|
|
31
|
+
: [
|
|
32
|
+
{ value: 'customer-request', label: 'CustomerRequest' },
|
|
33
|
+
{ value: 'not-available', label: 'NotAvailable' },
|
|
34
|
+
{ value: 'damaged-shipping', label: 'DamagedInShipping' },
|
|
35
|
+
{ value: 'wrong-item', label: 'WrongItem' },
|
|
36
|
+
{ value: 'other', label: 'Other' },
|
|
37
|
+
],
|
|
38
|
+
};
|
|
26
39
|
return {
|
|
27
40
|
api,
|
|
28
41
|
i18n,
|
|
42
|
+
orders,
|
|
29
43
|
};
|
|
30
44
|
}
|
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import type { Plugin } from 'vite';
|
|
2
|
+
/**
|
|
3
|
+
* Options for the linguiBabelPlugin.
|
|
4
|
+
*/
|
|
5
|
+
export interface LinguiBabelPluginOptions {
|
|
6
|
+
/**
|
|
7
|
+
* For testing: manually specify package paths that should have Lingui macros transformed.
|
|
8
|
+
* In production, these are automatically discovered from the VendureConfig plugins.
|
|
9
|
+
*/
|
|
10
|
+
additionalPackagePaths?: string[];
|
|
11
|
+
}
|
|
2
12
|
/**
|
|
3
13
|
* @description
|
|
4
14
|
* A custom Vite plugin that transforms Lingui macros in files using Babel instead of SWC.
|
|
@@ -17,11 +27,14 @@ import type { Plugin } from 'vite';
|
|
|
17
27
|
* - `@vendure/dashboard/src` files (in node_modules for external projects)
|
|
18
28
|
* - `packages/dashboard/src` files (in monorepo development)
|
|
19
29
|
* - User's dashboard extension files (e.g., custom plugins using Lingui)
|
|
30
|
+
* - Third-party npm packages that provide dashboard extensions (discovered automatically)
|
|
20
31
|
*
|
|
21
32
|
* Files NOT processed:
|
|
22
|
-
* -
|
|
33
|
+
* - Files that don't contain Lingui macro imports (fast check via string matching)
|
|
34
|
+
* - Non-JS/TS files
|
|
35
|
+
* - node_modules packages that are not discovered as Vendure plugins
|
|
23
36
|
*
|
|
24
37
|
* @see https://github.com/vendurehq/vendure/issues/3929
|
|
25
38
|
* @see https://github.com/lingui/swc-plugin/issues/179
|
|
26
39
|
*/
|
|
27
|
-
export declare function linguiBabelPlugin(): Plugin;
|
|
40
|
+
export declare function linguiBabelPlugin(options?: LinguiBabelPluginOptions): Plugin;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as babel from '@babel/core';
|
|
2
|
+
import { getConfigLoaderApi } from './vite-plugin-config-loader.js';
|
|
2
3
|
/**
|
|
3
4
|
* @description
|
|
4
5
|
* A custom Vite plugin that transforms Lingui macros in files using Babel instead of SWC.
|
|
@@ -17,19 +18,40 @@ import * as babel from '@babel/core';
|
|
|
17
18
|
* - `@vendure/dashboard/src` files (in node_modules for external projects)
|
|
18
19
|
* - `packages/dashboard/src` files (in monorepo development)
|
|
19
20
|
* - User's dashboard extension files (e.g., custom plugins using Lingui)
|
|
21
|
+
* - Third-party npm packages that provide dashboard extensions (discovered automatically)
|
|
20
22
|
*
|
|
21
23
|
* Files NOT processed:
|
|
22
|
-
* -
|
|
24
|
+
* - Files that don't contain Lingui macro imports (fast check via string matching)
|
|
25
|
+
* - Non-JS/TS files
|
|
26
|
+
* - node_modules packages that are not discovered as Vendure plugins
|
|
23
27
|
*
|
|
24
28
|
* @see https://github.com/vendurehq/vendure/issues/3929
|
|
25
29
|
* @see https://github.com/lingui/swc-plugin/issues/179
|
|
26
30
|
*/
|
|
27
|
-
export function linguiBabelPlugin() {
|
|
31
|
+
export function linguiBabelPlugin(options) {
|
|
32
|
+
var _a;
|
|
33
|
+
// Paths of npm packages that should have Lingui macros transformed.
|
|
34
|
+
// This is populated from plugin discovery when transform is first called.
|
|
35
|
+
const allowedNodeModulesPackages = new Set((_a = options === null || options === void 0 ? void 0 : options.additionalPackagePaths) !== null && _a !== void 0 ? _a : []);
|
|
36
|
+
// API reference to the config loader plugin (set in configResolved)
|
|
37
|
+
let configLoaderApi;
|
|
38
|
+
// Cached result from config loader (set on first transform that needs it)
|
|
39
|
+
let configResult;
|
|
28
40
|
return {
|
|
29
41
|
name: 'vendure:lingui-babel',
|
|
30
42
|
// Run BEFORE @vitejs/plugin-react so the macros are already transformed
|
|
31
43
|
// when the react plugin processes the file
|
|
32
44
|
enforce: 'pre',
|
|
45
|
+
configResolved({ plugins }) {
|
|
46
|
+
// Get reference to the config loader API.
|
|
47
|
+
// This doesn't load the config yet - that happens lazily in transform.
|
|
48
|
+
try {
|
|
49
|
+
configLoaderApi = getConfigLoaderApi(plugins);
|
|
50
|
+
}
|
|
51
|
+
catch (_a) {
|
|
52
|
+
// configLoaderPlugin not available (e.g., plugin used standalone for testing)
|
|
53
|
+
}
|
|
54
|
+
},
|
|
33
55
|
async transform(code, id) {
|
|
34
56
|
// Strip query params for path matching (Vite adds ?v=xxx for cache busting)
|
|
35
57
|
const cleanId = id.split('?')[0];
|
|
@@ -42,15 +64,42 @@ export function linguiBabelPlugin() {
|
|
|
42
64
|
if (!code.includes('@lingui/') || !code.includes('/macro')) {
|
|
43
65
|
return null;
|
|
44
66
|
}
|
|
45
|
-
//
|
|
46
|
-
// This ensures:
|
|
47
|
-
// 1. Dashboard source files get transformed (both in monorepo and external projects)
|
|
48
|
-
// 2. User's extension files get transformed (not in node_modules)
|
|
49
|
-
// 3. Other node_modules packages are left alone
|
|
67
|
+
// Check if this file should be transformed
|
|
50
68
|
if (cleanId.includes('node_modules')) {
|
|
69
|
+
// Always allow @vendure/dashboard source files
|
|
51
70
|
const isVendureDashboard = cleanId.includes('@vendure/dashboard/src') || cleanId.includes('packages/dashboard/src');
|
|
52
71
|
if (!isVendureDashboard) {
|
|
53
|
-
|
|
72
|
+
// Load discovered plugins on first need (lazy loading with caching)
|
|
73
|
+
if (configLoaderApi && !configResult) {
|
|
74
|
+
try {
|
|
75
|
+
configResult = await configLoaderApi.getVendureConfig();
|
|
76
|
+
// Extract package paths from discovered npm plugins
|
|
77
|
+
for (const plugin of configResult.pluginInfo) {
|
|
78
|
+
if (!plugin.sourcePluginPath && plugin.pluginPath.includes('node_modules')) {
|
|
79
|
+
const packagePath = extractPackagePath(plugin.pluginPath);
|
|
80
|
+
if (packagePath) {
|
|
81
|
+
allowedNodeModulesPackages.add(packagePath);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
// Log but continue - will use only manually specified paths
|
|
88
|
+
// eslint-disable-next-line no-console
|
|
89
|
+
console.warn('[vendure:lingui-babel] Failed to load plugin config:', error);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Check if this is from a discovered Vendure plugin package
|
|
93
|
+
let isDiscoveredPlugin = false;
|
|
94
|
+
for (const pkgPath of allowedNodeModulesPackages) {
|
|
95
|
+
if (cleanId.includes(pkgPath)) {
|
|
96
|
+
isDiscoveredPlugin = true;
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (!isDiscoveredPlugin) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
54
103
|
}
|
|
55
104
|
}
|
|
56
105
|
try {
|
|
@@ -84,3 +133,36 @@ export function linguiBabelPlugin() {
|
|
|
84
133
|
},
|
|
85
134
|
};
|
|
86
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Extracts the npm package name from a full file path.
|
|
138
|
+
*
|
|
139
|
+
* Examples:
|
|
140
|
+
* - /path/to/node_modules/@vendure-ee/plugin/dist/index.js -> @vendure-ee/plugin
|
|
141
|
+
* - /path/to/node_modules/some-plugin/lib/index.js -> some-plugin
|
|
142
|
+
* - /path/to/node_modules/.pnpm/@vendure-ee+plugin@1.0.0/node_modules/@vendure-ee/plugin/dist/index.js -> @vendure-ee/plugin
|
|
143
|
+
*/
|
|
144
|
+
function extractPackagePath(filePath) {
|
|
145
|
+
// Normalize path separators
|
|
146
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
147
|
+
// Find the last occurrence of node_modules (handles pnpm structure)
|
|
148
|
+
const lastNodeModulesIndex = normalizedPath.lastIndexOf('node_modules/');
|
|
149
|
+
if (lastNodeModulesIndex === -1) {
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
const afterNodeModules = normalizedPath.slice(lastNodeModulesIndex + 'node_modules/'.length);
|
|
153
|
+
// Handle scoped packages (@scope/package)
|
|
154
|
+
if (afterNodeModules.startsWith('@')) {
|
|
155
|
+
const parts = afterNodeModules.split('/');
|
|
156
|
+
if (parts.length >= 2) {
|
|
157
|
+
return `${parts[0]}/${parts[1]}`;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
// Unscoped package
|
|
162
|
+
const parts = afterNodeModules.split('/');
|
|
163
|
+
if (parts.length >= 1) {
|
|
164
|
+
return parts[0];
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
@@ -91,8 +91,8 @@ async function getPluginTranslations(pluginInfo) {
|
|
|
91
91
|
const poPatterns = path.join(dashboardPath, '**/*.po');
|
|
92
92
|
const translations = await glob(poPatterns, {
|
|
93
93
|
ignore: [
|
|
94
|
-
//
|
|
95
|
-
'**/node_modules
|
|
94
|
+
// Skip nested node_modules (transitive deps) but not .pnpm or .bun directories.
|
|
95
|
+
'**/node_modules/[!.]*/**/node_modules/**',
|
|
96
96
|
'**/*.spec.js',
|
|
97
97
|
'**/*.test.js',
|
|
98
98
|
],
|
|
@@ -103,6 +103,27 @@ export interface I18nConfig {
|
|
|
103
103
|
*/
|
|
104
104
|
availableLocales?: string[];
|
|
105
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* @description
|
|
108
|
+
* Options used by the {@link vendureDashboardPlugin} to configure order-related
|
|
109
|
+
* Dashboard UI behaviour.
|
|
110
|
+
*
|
|
111
|
+
* @docsCategory vite-plugin
|
|
112
|
+
* @docsPage vendureDashboardPlugin
|
|
113
|
+
* @since 3.4.0
|
|
114
|
+
*/
|
|
115
|
+
export interface OrdersConfig {
|
|
116
|
+
/**
|
|
117
|
+
* @description
|
|
118
|
+
* An array of refund reasons to display in the refund order dialog.
|
|
119
|
+
* Each reason has a `value` (used as the identifier) and a `label` (displayed to the user).
|
|
120
|
+
* If not provided, default reasons will be used.
|
|
121
|
+
*/
|
|
122
|
+
refundReasons?: Array<{
|
|
123
|
+
value: string;
|
|
124
|
+
label: string;
|
|
125
|
+
}>;
|
|
126
|
+
}
|
|
106
127
|
/**
|
|
107
128
|
* @description
|
|
108
129
|
* Options used by the {@link vendureDashboardPlugin} to configure aspects of the
|
|
@@ -123,6 +144,11 @@ export interface UiConfigPluginOptions {
|
|
|
123
144
|
* Configuration for internationalization settings
|
|
124
145
|
*/
|
|
125
146
|
i18n?: I18nConfig;
|
|
147
|
+
/**
|
|
148
|
+
* @description
|
|
149
|
+
* Configuration for order-related settings
|
|
150
|
+
*/
|
|
151
|
+
orders?: OrdersConfig;
|
|
126
152
|
}
|
|
127
153
|
/**
|
|
128
154
|
* @description
|
|
@@ -141,6 +167,11 @@ export interface ResolvedUiConfig {
|
|
|
141
167
|
* Note: defaultLocale remains optional as it can be undefined.
|
|
142
168
|
*/
|
|
143
169
|
i18n: Required<Omit<I18nConfig, 'defaultLocale'>> & Pick<I18nConfig, 'defaultLocale'>;
|
|
170
|
+
/**
|
|
171
|
+
* @description
|
|
172
|
+
* Order-related settings with all defaults applied
|
|
173
|
+
*/
|
|
174
|
+
orders: Required<OrdersConfig>;
|
|
144
175
|
}
|
|
145
176
|
/**
|
|
146
177
|
* This Vite plugin scans the configured plugins for any dashboard extensions and dynamically
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vendure/dashboard",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "3.5.3
|
|
4
|
+
"version": "3.5.3",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -157,8 +157,8 @@
|
|
|
157
157
|
"@storybook/addon-vitest": "^10.0.0-beta.9",
|
|
158
158
|
"@storybook/react-vite": "^10.0.0-beta.9",
|
|
159
159
|
"@types/node": "^22.13.4",
|
|
160
|
-
"@vendure/common": "
|
|
161
|
-
"@vendure/core": "
|
|
160
|
+
"@vendure/common": "3.5.3",
|
|
161
|
+
"@vendure/core": "3.5.3",
|
|
162
162
|
"@vitest/browser": "^3.2.4",
|
|
163
163
|
"@vitest/coverage-v8": "^3.2.4",
|
|
164
164
|
"eslint": "^9.19.0",
|
|
@@ -22,7 +22,9 @@ import {
|
|
|
22
22
|
} from '@/vdb/framework/layout-engine/page-layout.js';
|
|
23
23
|
import { detailPageRouteLoader } from '@/vdb/framework/page/detail-page-route-loader.js';
|
|
24
24
|
import { useDetailPage } from '@/vdb/framework/page/use-detail-page.js';
|
|
25
|
+
import { useJobQueuePolling } from '@/vdb/hooks/use-job-queue-polling.js';
|
|
25
26
|
import { Trans, useLingui } from '@lingui/react/macro';
|
|
27
|
+
import { useQueryClient } from '@tanstack/react-query';
|
|
26
28
|
import { createFileRoute, useNavigate } from '@tanstack/react-router';
|
|
27
29
|
import { toast } from 'sonner';
|
|
28
30
|
import {
|
|
@@ -54,6 +56,12 @@ function CollectionDetailPage() {
|
|
|
54
56
|
const navigate = useNavigate();
|
|
55
57
|
const creatingNewEntity = params.id === NEW_ENTITY_PATH;
|
|
56
58
|
const { t } = useLingui();
|
|
59
|
+
const queryClient = useQueryClient();
|
|
60
|
+
|
|
61
|
+
const { isPolling: pendingFilterApplication, startPolling } = useJobQueuePolling(
|
|
62
|
+
'apply-collection-filters',
|
|
63
|
+
() => queryClient.invalidateQueries({ queryKey: ['PaginatedListDataTable'] }),
|
|
64
|
+
);
|
|
57
65
|
|
|
58
66
|
const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
|
|
59
67
|
pageId,
|
|
@@ -79,6 +87,7 @@ function CollectionDetailPage() {
|
|
|
79
87
|
name: translation.name,
|
|
80
88
|
slug: translation.slug,
|
|
81
89
|
description: translation.description,
|
|
90
|
+
customFields: (translation as any).customFields,
|
|
82
91
|
})),
|
|
83
92
|
filters: entity.filters.map(f => ({
|
|
84
93
|
code: f.code,
|
|
@@ -90,12 +99,20 @@ function CollectionDetailPage() {
|
|
|
90
99
|
},
|
|
91
100
|
params: { id: params.id },
|
|
92
101
|
onSuccess: async data => {
|
|
102
|
+
const filtersWereDirty =
|
|
103
|
+
form.getFieldState('inheritFilters').isDirty || form.getFieldState('filters').isDirty;
|
|
93
104
|
toast(
|
|
94
105
|
creatingNewEntity ? t`Successfully created collection` : t`Successfully updated collection`,
|
|
95
106
|
);
|
|
96
107
|
resetForm();
|
|
108
|
+
if (filtersWereDirty) {
|
|
109
|
+
startPolling();
|
|
110
|
+
}
|
|
97
111
|
if (creatingNewEntity) {
|
|
98
|
-
await navigate({
|
|
112
|
+
await navigate({
|
|
113
|
+
to: `../$id`,
|
|
114
|
+
params: { id: data.id },
|
|
115
|
+
});
|
|
99
116
|
}
|
|
100
117
|
},
|
|
101
118
|
onError: err => {
|
|
@@ -106,7 +123,9 @@ function CollectionDetailPage() {
|
|
|
106
123
|
});
|
|
107
124
|
|
|
108
125
|
const shouldPreviewContents =
|
|
109
|
-
form.getFieldState('inheritFilters').isDirty ||
|
|
126
|
+
form.getFieldState('inheritFilters').isDirty ||
|
|
127
|
+
form.getFieldState('filters').isDirty ||
|
|
128
|
+
pendingFilterApplication;
|
|
110
129
|
|
|
111
130
|
const currentFiltersValue = form.watch('filters');
|
|
112
131
|
const currentInheritFiltersValue = form.watch('inheritFilters');
|
|
@@ -220,7 +239,7 @@ function CollectionDetailPage() {
|
|
|
220
239
|
</FormItem>
|
|
221
240
|
</PageBlock>
|
|
222
241
|
<PageBlock column="main" blockId="contents" title={<Trans>Contents</Trans>}>
|
|
223
|
-
{shouldPreviewContents || creatingNewEntity ? (
|
|
242
|
+
{pendingFilterApplication || shouldPreviewContents || creatingNewEntity ? (
|
|
224
243
|
<CollectionContentsPreviewTable
|
|
225
244
|
parentId={entity?.parent?.id}
|
|
226
245
|
filters={currentFiltersValue ?? []}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Alert, AlertDescription, AlertTitle } from '@/vdb/components/ui/alert.js';
|
|
2
|
+
import { Trans, useLingui } from '@lingui/react/macro';
|
|
3
|
+
import { AlertTriangle, CheckCircle } from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
export type DraftOrderStatusProps = Readonly<{
|
|
6
|
+
hasCustomer: boolean;
|
|
7
|
+
hasLines: boolean;
|
|
8
|
+
hasShippingMethod: boolean;
|
|
9
|
+
isDraftState: boolean;
|
|
10
|
+
}>;
|
|
11
|
+
|
|
12
|
+
export function DraftOrderStatus({
|
|
13
|
+
hasCustomer,
|
|
14
|
+
hasLines,
|
|
15
|
+
hasShippingMethod,
|
|
16
|
+
isDraftState,
|
|
17
|
+
}: DraftOrderStatusProps) {
|
|
18
|
+
const { t } = useLingui();
|
|
19
|
+
const isCompleteDraftDisabled = !hasCustomer || !hasLines || !hasShippingMethod || !isDraftState;
|
|
20
|
+
|
|
21
|
+
let completeDraftDisabledReason: string | null = null;
|
|
22
|
+
if (!hasCustomer) {
|
|
23
|
+
completeDraftDisabledReason = t`Select a customer to continue`;
|
|
24
|
+
} else if (!hasLines) {
|
|
25
|
+
completeDraftDisabledReason = t`Add at least one item to the order`;
|
|
26
|
+
} else if (!hasShippingMethod) {
|
|
27
|
+
completeDraftDisabledReason = t`Set a shipping address and select a shipping method`;
|
|
28
|
+
} else if (!isDraftState) {
|
|
29
|
+
completeDraftDisabledReason = t`Only draft orders can be completed`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const Icon = isCompleteDraftDisabled ? AlertTriangle : CheckCircle;
|
|
33
|
+
const title = isCompleteDraftDisabled ? (
|
|
34
|
+
<Trans>Order draft isn't ready to be completed</Trans>
|
|
35
|
+
) : (
|
|
36
|
+
<Trans>Order draft is ready to be completed</Trans>
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<Alert variant={isCompleteDraftDisabled ? 'destructive' : 'default'}>
|
|
41
|
+
<Icon className={isCompleteDraftDisabled ? '' : 'stroke-success'} />
|
|
42
|
+
<AlertTitle className={isCompleteDraftDisabled ? '' : 'text-success'}>{title}</AlertTitle>
|
|
43
|
+
{completeDraftDisabledReason ? (
|
|
44
|
+
<AlertDescription>{completeDraftDisabledReason}</AlertDescription>
|
|
45
|
+
) : null}
|
|
46
|
+
</Alert>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
@@ -218,8 +218,8 @@ export function FulfillOrderDialog({ order, onSuccess }: Readonly<FulfillOrderDi
|
|
|
218
218
|
>
|
|
219
219
|
<Trans>Fulfill order</Trans>
|
|
220
220
|
</Button>
|
|
221
|
-
<Dialog open={open}>
|
|
222
|
-
<DialogContent className="sm:max-w-[600px]">
|
|
221
|
+
<Dialog open={open} onOpenChange={setOpen}>
|
|
222
|
+
<DialogContent className="sm:max-w-[600px] max-h-[90vh] overflow-hidden flex flex-col">
|
|
223
223
|
<DialogHeader>
|
|
224
224
|
<DialogTitle>
|
|
225
225
|
<Trans>Fulfill order</Trans>
|
|
@@ -234,9 +234,9 @@ export function FulfillOrderDialog({ order, onSuccess }: Readonly<FulfillOrderDi
|
|
|
234
234
|
e.stopPropagation();
|
|
235
235
|
form.handleSubmit(handleSubmit)(e);
|
|
236
236
|
}}
|
|
237
|
-
className="space-y-4"
|
|
237
|
+
className="space-y-4 flex-1 overflow-hidden flex flex-col"
|
|
238
238
|
>
|
|
239
|
-
<div className="space-y-4">
|
|
239
|
+
<div className="space-y-4 flex-1 overflow-y-auto">
|
|
240
240
|
<div className="font-medium">
|
|
241
241
|
<Trans>Order lines</Trans>
|
|
242
242
|
</div>
|
|
@@ -19,15 +19,17 @@ import { Trans, useLingui } from '@lingui/react/macro';
|
|
|
19
19
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
20
20
|
import { Link, useNavigate } from '@tanstack/react-router';
|
|
21
21
|
import { ResultOf } from 'gql.tada';
|
|
22
|
-
import { Pencil, User } from 'lucide-react';
|
|
23
|
-
import { useMemo } from 'react';
|
|
22
|
+
import { Pencil, RotateCcw, User } from 'lucide-react';
|
|
23
|
+
import { useCallback, useMemo, useRef } from 'react';
|
|
24
24
|
import { toast } from 'sonner';
|
|
25
|
+
|
|
25
26
|
import {
|
|
26
27
|
orderDetailDocument,
|
|
27
28
|
setOrderCustomFieldsDocument,
|
|
28
29
|
transitionOrderToStateDocument,
|
|
29
30
|
} from '../orders.graphql.js';
|
|
30
|
-
import { canAddFulfillment, shouldShowAddManualPaymentButton } from '../utils/order-utils.js';
|
|
31
|
+
import { canAddFulfillment, canRefundOrder, shouldShowAddManualPaymentButton } from '../utils/order-utils.js';
|
|
32
|
+
|
|
31
33
|
import { AddManualPaymentDialog } from './add-manual-payment-dialog.js';
|
|
32
34
|
import { FulfillOrderDialog } from './fulfill-order-dialog.js';
|
|
33
35
|
import { FulfillmentDetails } from './fulfillment-details.js';
|
|
@@ -37,6 +39,7 @@ import { orderHistoryQueryKey } from './order-history/use-order-history.js';
|
|
|
37
39
|
import { OrderTable } from './order-table.js';
|
|
38
40
|
import { OrderTaxSummary } from './order-tax-summary.js';
|
|
39
41
|
import { PaymentDetails } from './payment-details.js';
|
|
42
|
+
import { RefundOrderDialog, RefundOrderDialogRef } from './refund-order-dialog.js';
|
|
40
43
|
import { getTypeForState, StateTransitionControl } from './state-transition-control.js';
|
|
41
44
|
import { useTransitionOrderToState } from './use-transition-order-to-state.js';
|
|
42
45
|
|
|
@@ -101,6 +104,15 @@ export function OrderDetailShared({
|
|
|
101
104
|
});
|
|
102
105
|
|
|
103
106
|
const customFieldConfig = useCustomFieldConfig('Order');
|
|
107
|
+
const refundDialogRef = useRef<RefundOrderDialogRef>(null);
|
|
108
|
+
|
|
109
|
+
const refreshOrderAndHistory = useCallback(async () => {
|
|
110
|
+
if (entity) {
|
|
111
|
+
const queryKey = getDetailQueryOptions(orderDetailDocument, { id: entity.id }).queryKey;
|
|
112
|
+
await queryClient.invalidateQueries({ queryKey });
|
|
113
|
+
void queryClient.refetchQueries({ queryKey: orderHistoryQueryKey(entity.id) });
|
|
114
|
+
}
|
|
115
|
+
}, [entity, queryClient]);
|
|
104
116
|
|
|
105
117
|
const stateTransitionActions = useMemo(() => {
|
|
106
118
|
if (!entity) {
|
|
@@ -116,17 +128,14 @@ export function OrderDetailShared({
|
|
|
116
128
|
description: transitionError,
|
|
117
129
|
});
|
|
118
130
|
} else {
|
|
119
|
-
refreshOrderAndHistory();
|
|
131
|
+
void refreshOrderAndHistory();
|
|
120
132
|
}
|
|
121
133
|
},
|
|
122
134
|
}));
|
|
123
|
-
}, [entity, transitionToState, t]);
|
|
135
|
+
}, [entity, transitionToState, t, refreshOrderAndHistory]);
|
|
124
136
|
|
|
125
|
-
|
|
126
|
-
return
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const handleModifyClick = async () => {
|
|
137
|
+
const handleModifyClick = useCallback(async () => {
|
|
138
|
+
if (!entity) return;
|
|
130
139
|
try {
|
|
131
140
|
await transitionOrderToStateMutation.mutateAsync({
|
|
132
141
|
id: entity.id,
|
|
@@ -140,19 +149,38 @@ export function OrderDetailShared({
|
|
|
140
149
|
description: error instanceof Error ? error.message : 'Unknown error',
|
|
141
150
|
});
|
|
142
151
|
}
|
|
143
|
-
};
|
|
152
|
+
}, [entity, transitionOrderToStateMutation, queryClient, navigate, t]);
|
|
153
|
+
|
|
154
|
+
const ModifyMenuItem = useCallback(
|
|
155
|
+
() => (
|
|
156
|
+
<DropdownMenuItem onClick={handleModifyClick}>
|
|
157
|
+
<Pencil className="w-4 h-4" />
|
|
158
|
+
<Trans>Modify</Trans>
|
|
159
|
+
</DropdownMenuItem>
|
|
160
|
+
),
|
|
161
|
+
[handleModifyClick],
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
const RefundMenuItem = useCallback(
|
|
165
|
+
() => (
|
|
166
|
+
<PermissionGuard requires={['UpdateOrder']}>
|
|
167
|
+
<DropdownMenuItem onClick={() => refundDialogRef.current?.open()}>
|
|
168
|
+
<RotateCcw className="w-4 h-4" />
|
|
169
|
+
<Trans>Refund & Cancel</Trans>
|
|
170
|
+
</DropdownMenuItem>
|
|
171
|
+
</PermissionGuard>
|
|
172
|
+
),
|
|
173
|
+
[],
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
if (!entity) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
144
179
|
|
|
145
180
|
const nextStates = entity.nextStates;
|
|
146
181
|
const showAddPaymentButton = shouldShowAddManualPaymentButton(entity);
|
|
147
182
|
const showFulfillButton = canAddFulfillment(entity);
|
|
148
|
-
|
|
149
|
-
async function refreshOrderAndHistory() {
|
|
150
|
-
if (entity) {
|
|
151
|
-
const queryKey = getDetailQueryOptions(orderDetailDocument, { id: entity.id }).queryKey;
|
|
152
|
-
await queryClient.invalidateQueries({ queryKey });
|
|
153
|
-
queryClient.refetchQueries({ queryKey: orderHistoryQueryKey(entity.id) });
|
|
154
|
-
}
|
|
155
|
-
}
|
|
183
|
+
const showRefundOption = canRefundOrder(entity);
|
|
156
184
|
|
|
157
185
|
return (
|
|
158
186
|
<Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
|
|
@@ -160,18 +188,8 @@ export function OrderDetailShared({
|
|
|
160
188
|
<PageActionBar>
|
|
161
189
|
<PageActionBarRight
|
|
162
190
|
dropdownMenuItems={[
|
|
163
|
-
...(nextStates.includes('Modifying')
|
|
164
|
-
|
|
165
|
-
{
|
|
166
|
-
component: () => (
|
|
167
|
-
<DropdownMenuItem onClick={handleModifyClick}>
|
|
168
|
-
<Pencil className="w-4 h-4" />
|
|
169
|
-
<Trans>Modify</Trans>
|
|
170
|
-
</DropdownMenuItem>
|
|
171
|
-
),
|
|
172
|
-
},
|
|
173
|
-
]
|
|
174
|
-
: []),
|
|
191
|
+
...(nextStates.includes('Modifying') ? [{ component: ModifyMenuItem }] : []),
|
|
192
|
+
...(showRefundOption ? [{ component: RefundMenuItem }] : []),
|
|
175
193
|
]}
|
|
176
194
|
>
|
|
177
195
|
{showAddPaymentButton && (
|
|
@@ -189,13 +207,22 @@ export function OrderDetailShared({
|
|
|
189
207
|
<FulfillOrderDialog
|
|
190
208
|
order={entity}
|
|
191
209
|
onSuccess={() => {
|
|
192
|
-
refreshOrderAndHistory();
|
|
210
|
+
void refreshOrderAndHistory();
|
|
193
211
|
}}
|
|
194
212
|
/>
|
|
195
213
|
</PermissionGuard>
|
|
196
214
|
)}
|
|
197
215
|
</PageActionBarRight>
|
|
198
216
|
</PageActionBar>
|
|
217
|
+
{showRefundOption && (
|
|
218
|
+
<RefundOrderDialog
|
|
219
|
+
ref={refundDialogRef}
|
|
220
|
+
order={entity}
|
|
221
|
+
onSuccess={() => {
|
|
222
|
+
void refreshOrderAndHistory();
|
|
223
|
+
}}
|
|
224
|
+
/>
|
|
225
|
+
)}
|
|
199
226
|
<PageLayout>
|
|
200
227
|
{/* Main Column Blocks */}
|
|
201
228
|
{beforeOrderTable?.(entity)}
|
|
@@ -288,7 +315,7 @@ export function OrderDetailShared({
|
|
|
288
315
|
fulfillment={fulfillment}
|
|
289
316
|
onSuccess={() => {
|
|
290
317
|
refreshEntity();
|
|
291
|
-
queryClient.refetchQueries({
|
|
318
|
+
void queryClient.refetchQueries({
|
|
292
319
|
queryKey: orderHistoryQueryKey(entity.id),
|
|
293
320
|
});
|
|
294
321
|
}}
|