@wilshop/dashboard 3.5.6 → 3.5.7
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/dashboard.plugin.d.ts +1 -1
- package/dist/plugin/dashboard.plugin.js +1 -1
- package/dist/vite/utils/compiler.js +50 -24
- package/dist/vite/utils/path-transformer.d.ts +20 -0
- package/dist/vite/utils/path-transformer.js +116 -0
- package/dist/vite/utils/plugin-discovery.js +3 -2
- 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 +10 -6
- package/src/app/common/delete-bulk-action.tsx +1 -1
- package/src/app/common/duplicate-bulk-action.tsx +1 -1
- package/src/app/routes/_authenticated/_collections/collections.graphql.ts +1 -3
- package/src/app/routes/_authenticated/_collections/collections.tsx +169 -48
- package/src/app/routes/_authenticated/_collections/collections_.$id.tsx +36 -5
- package/src/app/routes/_authenticated/_collections/components/collection-bulk-actions.tsx +1 -1
- package/src/app/routes/_authenticated/_collections/components/collection-filters-selector.tsx +7 -1
- package/src/app/routes/_authenticated/_customers/components/customer-history/default-customer-history-components.tsx +31 -29
- 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/_global-settings/global-settings.tsx +1 -1
- package/src/app/routes/_authenticated/_orders/components/draft-order-status.tsx +48 -0
- package/src/app/routes/_authenticated/_orders/components/fulfill-order-dialog.tsx +8 -5
- package/src/app/routes/_authenticated/_orders/components/order-detail-shared.tsx +79 -54
- 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/components/payment-eligibility-checker-selector.tsx +4 -1
- package/src/app/routes/_authenticated/_payment-methods/components/payment-handler-selector.tsx +7 -1
- package/src/app/routes/_authenticated/_payment-methods/payment-methods_.$id.tsx +18 -2
- package/src/app/routes/_authenticated/_product-variants/components/product-variant-bulk-actions.tsx +1 -1
- package/src/app/routes/_authenticated/_product-variants/components/variant-price-detail.tsx +6 -2
- 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/_products/components/product-bulk-actions.tsx +1 -1
- 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/components/promotion-actions-selector.tsx +7 -1
- package/src/app/routes/_authenticated/_promotions/components/promotion-conditions-selector.tsx +7 -1
- package/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx +18 -2
- package/src/app/routes/_authenticated/_shipping-methods/components/shipping-calculator-selector.tsx +7 -1
- package/src/app/routes/_authenticated/_shipping-methods/components/shipping-eligibility-checker-selector.tsx +4 -1
- package/src/app/routes/_authenticated/_shipping-methods/shipping-methods_.$id.tsx +14 -2
- package/src/i18n/common-strings.ts +7 -0
- package/src/i18n/locales/ar.po +669 -399
- package/src/i18n/locales/bg.po +1889 -46
- package/src/i18n/locales/cs.po +676 -406
- package/src/i18n/locales/de.po +676 -406
- package/src/i18n/locales/en.po +669 -399
- package/src/i18n/locales/es.po +676 -406
- package/src/i18n/locales/fa.po +676 -406
- package/src/i18n/locales/fr.po +676 -406
- package/src/i18n/locales/he.po +676 -406
- package/src/i18n/locales/hr.po +676 -406
- package/src/i18n/locales/it.po +676 -406
- package/src/i18n/locales/ja.po +676 -406
- package/src/i18n/locales/nb.po +676 -406
- package/src/i18n/locales/ne.po +676 -406
- package/src/i18n/locales/pl.po +676 -406
- package/src/i18n/locales/pt_BR.po +676 -406
- package/src/i18n/locales/pt_PT.po +676 -406
- package/src/i18n/locales/ru.po +676 -406
- package/src/i18n/locales/sv.po +676 -406
- package/src/i18n/locales/tr.po +676 -406
- package/src/i18n/locales/uk.po +676 -406
- package/src/i18n/locales/zh_Hans.po +676 -406
- package/src/i18n/locales/zh_Hant.po +676 -406
- package/src/lib/components/data-input/facet-value-input.tsx +2 -2
- package/src/lib/components/data-input/index.ts +1 -0
- package/src/lib/components/data-input/select-with-options.tsx +23 -7
- package/src/lib/components/data-input/struct-form-input.tsx +53 -21
- package/src/lib/components/data-input/text-input.tsx +1 -1
- package/src/lib/components/data-table/data-table-bulk-actions.tsx +2 -1
- package/src/lib/components/data-table/data-table-context.tsx +2 -10
- package/src/lib/components/data-table/data-table-utils.ts +34 -12
- package/src/lib/components/data-table/data-table.tsx +68 -30
- package/src/lib/components/data-table/global-views-bar.tsx +1 -1
- package/src/lib/components/data-table/my-views-button.tsx +1 -1
- package/src/lib/components/data-table/save-view-button.tsx +1 -1
- package/src/lib/components/data-table/use-generated-columns.tsx +9 -2
- package/src/lib/components/data-table/views-sheet.tsx +1 -1
- package/src/lib/components/layout/channel-switcher.tsx +16 -17
- package/src/lib/components/layout/manage-languages-dialog.tsx +1 -1
- package/src/lib/components/shared/assign-to-channel-bulk-action.tsx +1 -1
- package/src/lib/components/shared/configurable-operation-input.tsx +23 -0
- package/src/lib/components/shared/configurable-operation-multi-selector.tsx +45 -0
- package/src/lib/components/shared/configurable-operation-selector.tsx +5 -0
- package/src/lib/components/shared/paginated-list-context.ts +10 -0
- package/src/lib/components/shared/paginated-list-data-table.tsx +6 -32
- package/src/lib/components/shared/remove-from-channel-bulk-action.tsx +1 -1
- package/src/lib/components/ui/alert.tsx +2 -0
- package/src/lib/constants.ts +7 -319
- package/src/lib/framework/dashboard-widget/base-widget.tsx +3 -12
- package/src/lib/framework/dashboard-widget/latest-orders-widget/index.tsx +1 -1
- package/src/lib/framework/dashboard-widget/metrics-widget/chart.tsx +1 -1
- package/src/lib/framework/dashboard-widget/metrics-widget/index.tsx +1 -1
- package/src/lib/framework/dashboard-widget/orders-summary/index.tsx +1 -1
- package/src/lib/framework/dashboard-widget/widget-filters-context.tsx +2 -20
- package/src/lib/framework/extension-api/input-component-extensions.tsx +4 -0
- package/src/lib/framework/form-engine/custom-form-component.tsx +13 -3
- package/src/lib/framework/form-engine/form-engine-types.ts +3 -5
- package/src/lib/framework/form-engine/form-schema-tools.ts +4 -1
- package/src/lib/framework/form-engine/use-generated-form.tsx +6 -2
- package/src/lib/framework/form-engine/utils.spec.ts +129 -2
- package/src/lib/framework/form-engine/utils.ts +36 -9
- package/src/lib/framework/form-engine/value-transformers.ts +6 -0
- 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 +33 -16
- package/src/lib/graphql/schema-enums.ts +13 -0
- package/src/lib/hooks/use-alerts-context.ts +10 -0
- package/src/lib/hooks/use-alerts.ts +1 -1
- package/src/lib/hooks/use-data-table-context.ts +11 -0
- package/src/lib/hooks/use-dynamic-translations.ts +7 -0
- package/src/lib/hooks/use-job-queue-polling.ts +160 -0
- package/src/lib/hooks/use-paginated-list.ts +28 -0
- package/src/lib/hooks/use-widget-dimensions.ts +12 -0
- package/src/lib/hooks/use-widget-filters.ts +21 -0
- package/src/lib/index.ts +12 -0
- package/src/lib/providers/alerts-provider.tsx +3 -11
- package/src/lib/virtual.d.ts +5 -0
- package/src/lib/utils/global-languages.ts +0 -268
|
@@ -41,49 +41,124 @@ export const Route = createFileRoute('/_authenticated/_collections/collections')
|
|
|
41
41
|
|
|
42
42
|
type Collection = ResultOf<typeof collectionListDocument>['collections']['items'][number];
|
|
43
43
|
|
|
44
|
+
const CHILDREN_PAGE_SIZE = 20;
|
|
45
|
+
|
|
46
|
+
type LoadMoreRow = {
|
|
47
|
+
_isLoadMore: true;
|
|
48
|
+
_parentId: string;
|
|
49
|
+
_totalItems: number;
|
|
50
|
+
_loadedItems: number;
|
|
51
|
+
id: string;
|
|
52
|
+
breadcrumbs: { id: string; name: string; slug: string }[];
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
type CollectionOrLoadMore = Collection | LoadMoreRow;
|
|
56
|
+
|
|
57
|
+
function isLoadMoreRow(row: CollectionOrLoadMore): row is LoadMoreRow {
|
|
58
|
+
return '_isLoadMore' in row && row._isLoadMore === true;
|
|
59
|
+
}
|
|
60
|
+
|
|
44
61
|
function CollectionListPage() {
|
|
45
62
|
const { t } = useLingui();
|
|
46
63
|
const queryClient = useQueryClient();
|
|
47
64
|
const [expanded, setExpanded] = useState<ExpandedState>({});
|
|
48
65
|
const [searchTerm, setSearchTerm] = useState<string>('');
|
|
66
|
+
const [accumulatedChildren, setAccumulatedChildren] = useState<
|
|
67
|
+
Record<string, { items: Collection[]; totalItems: number }>
|
|
68
|
+
>({});
|
|
69
|
+
const [nextPageToFetch, setNextPageToFetch] = useState<Record<string, number>>({});
|
|
49
70
|
|
|
50
|
-
|
|
51
|
-
queries: Object.entries(expanded)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
71
|
+
useQueries({
|
|
72
|
+
queries: expanded === true ? [] : Object.entries(expanded)
|
|
73
|
+
.filter(([collectionId]) => !accumulatedChildren[collectionId])
|
|
74
|
+
.map(([collectionId]) => {
|
|
75
|
+
return {
|
|
76
|
+
queryKey: ['childCollections', collectionId, 'page', 0],
|
|
77
|
+
queryFn: async () => {
|
|
78
|
+
const result = await api.query(collectionListDocument, {
|
|
79
|
+
options: {
|
|
80
|
+
filter: {
|
|
81
|
+
parentId: { eq: collectionId },
|
|
82
|
+
},
|
|
83
|
+
take: CHILDREN_PAGE_SIZE,
|
|
84
|
+
skip: 0,
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
setAccumulatedChildren(prev => ({
|
|
88
|
+
...prev,
|
|
89
|
+
[collectionId]: {
|
|
90
|
+
items: result.collections.items,
|
|
91
|
+
totalItems: result.collections.totalItems,
|
|
59
92
|
},
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
93
|
+
}));
|
|
94
|
+
return result;
|
|
95
|
+
},
|
|
96
|
+
staleTime: 1000 * 60 * 5,
|
|
97
|
+
} satisfies FetchQueryOptions;
|
|
98
|
+
}),
|
|
65
99
|
});
|
|
66
100
|
|
|
67
|
-
|
|
68
|
-
(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
101
|
+
useQueries({
|
|
102
|
+
queries: Object.entries(nextPageToFetch)
|
|
103
|
+
.filter(([_, page]) => page > 0)
|
|
104
|
+
.map(([collectionId, page]) => {
|
|
105
|
+
return {
|
|
106
|
+
queryKey: ['childCollections', collectionId, 'page', page],
|
|
107
|
+
queryFn: async () => {
|
|
108
|
+
const result = await api.query(collectionListDocument, {
|
|
109
|
+
options: {
|
|
110
|
+
filter: {
|
|
111
|
+
parentId: { eq: collectionId },
|
|
112
|
+
},
|
|
113
|
+
take: CHILDREN_PAGE_SIZE,
|
|
114
|
+
skip: page * CHILDREN_PAGE_SIZE,
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
setAccumulatedChildren(prev => {
|
|
118
|
+
const existing = prev[collectionId];
|
|
119
|
+
if (!existing) return prev;
|
|
120
|
+
return {
|
|
121
|
+
...prev,
|
|
122
|
+
[collectionId]: {
|
|
123
|
+
items: [...existing.items, ...result.collections.items],
|
|
124
|
+
totalItems: result.collections.totalItems,
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
});
|
|
128
|
+
setNextPageToFetch(prev => {
|
|
129
|
+
const { [collectionId]: _, ...rest } = prev;
|
|
130
|
+
return rest;
|
|
131
|
+
});
|
|
132
|
+
return result;
|
|
133
|
+
},
|
|
134
|
+
staleTime: 1000 * 60 * 5,
|
|
135
|
+
} satisfies FetchQueryOptions;
|
|
136
|
+
}),
|
|
137
|
+
});
|
|
77
138
|
|
|
78
|
-
const addSubCollections = (data: Collection[]) => {
|
|
79
|
-
const allRows
|
|
139
|
+
const addSubCollections = (data: Collection[]): CollectionOrLoadMore[] => {
|
|
140
|
+
const allRows: CollectionOrLoadMore[] = [];
|
|
80
141
|
const addSubRows = (row: Collection) => {
|
|
81
|
-
const
|
|
82
|
-
if (
|
|
83
|
-
|
|
142
|
+
const isExpanded = expanded === true || (typeof expanded === 'object' && expanded[row.id]);
|
|
143
|
+
if (!isExpanded) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const childData = accumulatedChildren[row.id];
|
|
147
|
+
if (childData?.items.length) {
|
|
148
|
+
for (const subRow of childData.items) {
|
|
84
149
|
allRows.push(subRow);
|
|
85
150
|
addSubRows(subRow);
|
|
86
151
|
}
|
|
152
|
+
if (childData.totalItems > childData.items.length) {
|
|
153
|
+
allRows.push({
|
|
154
|
+
_isLoadMore: true,
|
|
155
|
+
_parentId: row.id,
|
|
156
|
+
_totalItems: childData.totalItems,
|
|
157
|
+
_loadedItems: childData.items.length,
|
|
158
|
+
id: `load-more-${row.id}`,
|
|
159
|
+
breadcrumbs: [...(row.breadcrumbs || []), { id: row.id, name: row.name, slug: row.slug }],
|
|
160
|
+
});
|
|
161
|
+
}
|
|
87
162
|
}
|
|
88
163
|
};
|
|
89
164
|
data.forEach(row => {
|
|
@@ -93,37 +168,56 @@ function CollectionListPage() {
|
|
|
93
168
|
return allRows;
|
|
94
169
|
};
|
|
95
170
|
|
|
171
|
+
const handleLoadMoreChildren = (parentId: string) => {
|
|
172
|
+
const currentItems = accumulatedChildren[parentId]?.items.length ?? 0;
|
|
173
|
+
const nextPage = Math.floor(currentItems / CHILDREN_PAGE_SIZE);
|
|
174
|
+
setNextPageToFetch(prev => ({
|
|
175
|
+
...prev,
|
|
176
|
+
[parentId]: nextPage,
|
|
177
|
+
}));
|
|
178
|
+
};
|
|
179
|
+
|
|
96
180
|
const handleReorder = async (oldIndex: number, newIndex: number, item: Collection, allItems?: Collection[]) => {
|
|
181
|
+
if (isLoadMoreRow(item as CollectionOrLoadMore)) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
97
184
|
try {
|
|
98
|
-
const
|
|
185
|
+
const rawItems = (allItems || []) as CollectionOrLoadMore[];
|
|
186
|
+
|
|
187
|
+
// Filter out LoadMoreRows - they shouldn't affect position calculations
|
|
188
|
+
const items = rawItems.filter((i): i is Collection => !isLoadMoreRow(i));
|
|
189
|
+
|
|
190
|
+
// Recalculate indices in the filtered array
|
|
191
|
+
const adjustedOldIndex = items.findIndex(i => i.id === item.id);
|
|
192
|
+
const targetItem = rawItems[newIndex];
|
|
193
|
+
const adjustedNewIndex = isLoadMoreRow(targetItem)
|
|
194
|
+
? items.findIndex(i => i.id === targetItem._parentId)
|
|
195
|
+
: items.findIndex(i => i.id === (targetItem as Collection).id);
|
|
196
|
+
|
|
99
197
|
const sourceParentId = getItemParentId(item);
|
|
100
198
|
|
|
101
199
|
if (!sourceParentId) {
|
|
102
200
|
throw new Error('Unable to determine parent collection ID');
|
|
103
201
|
}
|
|
104
202
|
|
|
105
|
-
// Calculate target position (parent and index)
|
|
106
203
|
const { targetParentId, adjustedIndex: initialIndex } = calculateDragTargetPosition({
|
|
107
204
|
item,
|
|
108
|
-
oldIndex,
|
|
109
|
-
newIndex,
|
|
205
|
+
oldIndex: adjustedOldIndex,
|
|
206
|
+
newIndex: adjustedNewIndex,
|
|
110
207
|
items,
|
|
111
208
|
sourceParentId,
|
|
112
209
|
expanded,
|
|
113
210
|
});
|
|
114
211
|
|
|
115
|
-
// Validate no circular references when moving to different parent
|
|
116
212
|
if (targetParentId !== sourceParentId && isCircularReference(item, targetParentId, items)) {
|
|
117
213
|
toast.error(t`Cannot move a collection into its own descendant`);
|
|
118
214
|
throw new Error('Circular reference detected');
|
|
119
215
|
}
|
|
120
216
|
|
|
121
|
-
// Calculate final index (adjust for same-parent moves)
|
|
122
217
|
const adjustedIndex = targetParentId === sourceParentId
|
|
123
|
-
? calculateSiblingIndex({ item, oldIndex, newIndex, items, parentId: sourceParentId })
|
|
218
|
+
? calculateSiblingIndex({ item, oldIndex: adjustedOldIndex, newIndex: adjustedNewIndex, items, parentId: sourceParentId })
|
|
124
219
|
: initialIndex;
|
|
125
220
|
|
|
126
|
-
// Perform the move
|
|
127
221
|
await api.mutate(moveCollectionDocument, {
|
|
128
222
|
input: {
|
|
129
223
|
collectionId: item.id,
|
|
@@ -132,7 +226,15 @@ function CollectionListPage() {
|
|
|
132
226
|
},
|
|
133
227
|
});
|
|
134
228
|
|
|
135
|
-
|
|
229
|
+
setAccumulatedChildren(prev => {
|
|
230
|
+
const newState = { ...prev };
|
|
231
|
+
delete newState[sourceParentId];
|
|
232
|
+
if (targetParentId !== sourceParentId) {
|
|
233
|
+
delete newState[targetParentId];
|
|
234
|
+
}
|
|
235
|
+
return newState;
|
|
236
|
+
});
|
|
237
|
+
|
|
136
238
|
const queriesToInvalidate = [
|
|
137
239
|
queryClient.invalidateQueries({ queryKey: ['childCollections', sourceParentId] }),
|
|
138
240
|
queryClient.invalidateQueries({ queryKey: ['PaginatedListDataTable'] }),
|
|
@@ -178,11 +280,12 @@ function CollectionListPage() {
|
|
|
178
280
|
dependencies: ['children', 'breadcrumbs'],
|
|
179
281
|
},
|
|
180
282
|
cell: ({ row }) => {
|
|
283
|
+
const original = row.original as Collection;
|
|
181
284
|
const isExpanded = row.getIsExpanded();
|
|
182
|
-
const hasChildren = !!
|
|
285
|
+
const hasChildren = !!original.children?.length;
|
|
183
286
|
return (
|
|
184
287
|
<div
|
|
185
|
-
style={{ marginLeft: (
|
|
288
|
+
style={{ marginLeft: (original.breadcrumbs?.length - 2) * 20 + 'px' }}
|
|
186
289
|
className="flex gap-2 items-center"
|
|
187
290
|
>
|
|
188
291
|
<Button
|
|
@@ -194,7 +297,7 @@ function CollectionListPage() {
|
|
|
194
297
|
>
|
|
195
298
|
{isExpanded ? <FolderOpen /> : <Folder />}
|
|
196
299
|
</Button>
|
|
197
|
-
<DetailPageButton id={
|
|
300
|
+
<DetailPageButton id={original.id} label={original.name} />
|
|
198
301
|
</div>
|
|
199
302
|
);
|
|
200
303
|
},
|
|
@@ -218,7 +321,7 @@ function CollectionListPage() {
|
|
|
218
321
|
);
|
|
219
322
|
},
|
|
220
323
|
},
|
|
221
|
-
|
|
324
|
+
productVariantCount: {
|
|
222
325
|
header: () => <Trans>Contents</Trans>,
|
|
223
326
|
cell: ({ row }) => {
|
|
224
327
|
return (
|
|
@@ -226,7 +329,7 @@ function CollectionListPage() {
|
|
|
226
329
|
collectionId={row.original.id}
|
|
227
330
|
collectionName={row.original.name}
|
|
228
331
|
>
|
|
229
|
-
<Trans>{row.original.
|
|
332
|
+
<Trans>{row.original.productVariantCount} variants</Trans>
|
|
230
333
|
</CollectionContentsSheet>
|
|
231
334
|
);
|
|
232
335
|
},
|
|
@@ -257,7 +360,7 @@ function CollectionListPage() {
|
|
|
257
360
|
'name',
|
|
258
361
|
'slug',
|
|
259
362
|
'breadcrumbs',
|
|
260
|
-
'
|
|
363
|
+
'productVariantCount',
|
|
261
364
|
]}
|
|
262
365
|
transformData={data => {
|
|
263
366
|
return addSubCollections(data);
|
|
@@ -270,12 +373,30 @@ function CollectionListPage() {
|
|
|
270
373
|
options.onExpandedChange = setExpanded;
|
|
271
374
|
options.getExpandedRowModel = getExpandedRowModel();
|
|
272
375
|
options.getRowCanExpand = () => true;
|
|
273
|
-
options.getRowId = row =>
|
|
274
|
-
|
|
275
|
-
};
|
|
376
|
+
options.getRowId = row => row.id;
|
|
377
|
+
options.enableRowSelection = row => !isLoadMoreRow(row.original);
|
|
276
378
|
options.meta = {
|
|
277
379
|
...options.meta,
|
|
278
380
|
resetExpanded: () => setExpanded({}),
|
|
381
|
+
isUtilityRow: (row: { original: CollectionOrLoadMore }) => isLoadMoreRow(row.original),
|
|
382
|
+
renderUtilityRow: (row: { original: CollectionOrLoadMore }) => {
|
|
383
|
+
const original = row.original as LoadMoreRow;
|
|
384
|
+
const remaining = original._totalItems - original._loadedItems;
|
|
385
|
+
return (
|
|
386
|
+
<div
|
|
387
|
+
style={{ paddingLeft: (original.breadcrumbs?.length - 1) * 20 + 'px' }}
|
|
388
|
+
className="flex justify-center py-2"
|
|
389
|
+
>
|
|
390
|
+
<Button
|
|
391
|
+
size="sm"
|
|
392
|
+
variant="outline"
|
|
393
|
+
onClick={() => handleLoadMoreChildren(original._parentId)}
|
|
394
|
+
>
|
|
395
|
+
<Trans>Load {Math.min(remaining, CHILDREN_PAGE_SIZE)} more ({remaining} remaining)</Trans>
|
|
396
|
+
</Button>
|
|
397
|
+
</div>
|
|
398
|
+
);
|
|
399
|
+
},
|
|
279
400
|
};
|
|
280
401
|
return options;
|
|
281
402
|
}}
|
|
@@ -319,7 +440,7 @@ function CollectionListPage() {
|
|
|
319
440
|
},
|
|
320
441
|
]}
|
|
321
442
|
onReorder={handleReorder}
|
|
322
|
-
disableDragAndDrop={!!searchTerm}
|
|
443
|
+
disableDragAndDrop={!!searchTerm}
|
|
323
444
|
>
|
|
324
445
|
<PageActionBarRight>
|
|
325
446
|
<PermissionGuard requires={['CreateCollection', 'CreateCatalog']}>
|
|
@@ -22,8 +22,11 @@ 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';
|
|
29
|
+
import { useState } from 'react';
|
|
27
30
|
import { toast } from 'sonner';
|
|
28
31
|
import {
|
|
29
32
|
collectionDetailDocument,
|
|
@@ -54,6 +57,12 @@ function CollectionDetailPage() {
|
|
|
54
57
|
const navigate = useNavigate();
|
|
55
58
|
const creatingNewEntity = params.id === NEW_ENTITY_PATH;
|
|
56
59
|
const { t } = useLingui();
|
|
60
|
+
const queryClient = useQueryClient();
|
|
61
|
+
|
|
62
|
+
const { isPolling: pendingFilterApplication, startPolling } = useJobQueuePolling(
|
|
63
|
+
'apply-collection-filters',
|
|
64
|
+
() => queryClient.invalidateQueries({ queryKey: ['PaginatedListDataTable'] }),
|
|
65
|
+
);
|
|
57
66
|
|
|
58
67
|
const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
|
|
59
68
|
pageId,
|
|
@@ -79,6 +88,7 @@ function CollectionDetailPage() {
|
|
|
79
88
|
name: translation.name,
|
|
80
89
|
slug: translation.slug,
|
|
81
90
|
description: translation.description,
|
|
91
|
+
customFields: (translation as any).customFields,
|
|
82
92
|
})),
|
|
83
93
|
filters: entity.filters.map(f => ({
|
|
84
94
|
code: f.code,
|
|
@@ -90,12 +100,20 @@ function CollectionDetailPage() {
|
|
|
90
100
|
},
|
|
91
101
|
params: { id: params.id },
|
|
92
102
|
onSuccess: async data => {
|
|
103
|
+
const filtersWereDirty =
|
|
104
|
+
form.getFieldState('inheritFilters').isDirty || form.getFieldState('filters').isDirty;
|
|
93
105
|
toast(
|
|
94
106
|
creatingNewEntity ? t`Successfully created collection` : t`Successfully updated collection`,
|
|
95
107
|
);
|
|
96
108
|
resetForm();
|
|
109
|
+
if (filtersWereDirty) {
|
|
110
|
+
startPolling();
|
|
111
|
+
}
|
|
97
112
|
if (creatingNewEntity) {
|
|
98
|
-
await navigate({
|
|
113
|
+
await navigate({
|
|
114
|
+
to: `../$id`,
|
|
115
|
+
params: { id: data.id },
|
|
116
|
+
});
|
|
99
117
|
}
|
|
100
118
|
},
|
|
101
119
|
onError: err => {
|
|
@@ -106,11 +124,15 @@ function CollectionDetailPage() {
|
|
|
106
124
|
});
|
|
107
125
|
|
|
108
126
|
const shouldPreviewContents =
|
|
109
|
-
form.getFieldState('inheritFilters').isDirty ||
|
|
127
|
+
form.getFieldState('inheritFilters').isDirty ||
|
|
128
|
+
form.getFieldState('filters').isDirty ||
|
|
129
|
+
pendingFilterApplication;
|
|
110
130
|
|
|
111
131
|
const currentFiltersValue = form.watch('filters');
|
|
112
132
|
const currentInheritFiltersValue = form.watch('inheritFilters');
|
|
113
133
|
|
|
134
|
+
const [filtersArgsValid, setFiltersArgsValid] = useState(true);
|
|
135
|
+
|
|
114
136
|
return (
|
|
115
137
|
<Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
|
|
116
138
|
<PageTitle>{creatingNewEntity ? <Trans>New collection</Trans> : (entity?.name ?? '')}</PageTitle>
|
|
@@ -119,7 +141,12 @@ function CollectionDetailPage() {
|
|
|
119
141
|
<PermissionGuard requires={['UpdateCollection', 'UpdateCatalog']}>
|
|
120
142
|
<Button
|
|
121
143
|
type="submit"
|
|
122
|
-
disabled={
|
|
144
|
+
disabled={
|
|
145
|
+
!form.formState.isDirty ||
|
|
146
|
+
!form.formState.isValid ||
|
|
147
|
+
isPending ||
|
|
148
|
+
!filtersArgsValid
|
|
149
|
+
}
|
|
123
150
|
>
|
|
124
151
|
{creatingNewEntity ? <Trans>Create</Trans> : <Trans>Update</Trans>}
|
|
125
152
|
</Button>
|
|
@@ -188,7 +215,11 @@ function CollectionDetailPage() {
|
|
|
188
215
|
control={form.control}
|
|
189
216
|
name="filters"
|
|
190
217
|
render={({ field }) => (
|
|
191
|
-
<CollectionFiltersSelector
|
|
218
|
+
<CollectionFiltersSelector
|
|
219
|
+
value={field.value ?? []}
|
|
220
|
+
onChange={field.onChange}
|
|
221
|
+
onValidityChange={setFiltersArgsValid}
|
|
222
|
+
/>
|
|
192
223
|
)}
|
|
193
224
|
/>
|
|
194
225
|
</PageBlock>
|
|
@@ -220,7 +251,7 @@ function CollectionDetailPage() {
|
|
|
220
251
|
</FormItem>
|
|
221
252
|
</PageBlock>
|
|
222
253
|
<PageBlock column="main" blockId="contents" title={<Trans>Contents</Trans>}>
|
|
223
|
-
{shouldPreviewContents || creatingNewEntity ? (
|
|
254
|
+
{pendingFilterApplication || shouldPreviewContents || creatingNewEntity ? (
|
|
224
255
|
<CollectionContentsPreviewTable
|
|
225
256
|
parentId={entity?.parent?.id}
|
|
226
257
|
filters={currentFiltersValue ?? []}
|
|
@@ -4,7 +4,7 @@ import { useState } from 'react';
|
|
|
4
4
|
|
|
5
5
|
import { DataTableBulkActionItem } from '@/vdb/components/data-table/data-table-bulk-action-item.js';
|
|
6
6
|
import { AssignToChannelBulkAction } from '@/vdb/components/shared/assign-to-channel-bulk-action.js';
|
|
7
|
-
import { usePaginatedList } from '@/vdb/
|
|
7
|
+
import { usePaginatedList } from '@/vdb/hooks/use-paginated-list.js';
|
|
8
8
|
import { RemoveFromChannelBulkAction } from '@/vdb/components/shared/remove-from-channel-bulk-action.js';
|
|
9
9
|
import { BulkActionComponent } from '@/vdb/framework/extension-api/types/data-table.js';
|
|
10
10
|
import { api } from '@/vdb/graphql/api.js';
|
package/src/app/routes/_authenticated/_collections/components/collection-filters-selector.tsx
CHANGED
|
@@ -5,9 +5,14 @@ import { getCollectionFiltersQueryOptions } from '../collections.graphql.js';
|
|
|
5
5
|
export interface CollectionFiltersSelectorProps {
|
|
6
6
|
value: ConfigurableOperationInputType[];
|
|
7
7
|
onChange: (filters: ConfigurableOperationInputType[]) => void;
|
|
8
|
+
onValidityChange?: (isValid: boolean) => void;
|
|
8
9
|
}
|
|
9
10
|
|
|
10
|
-
export function CollectionFiltersSelector({
|
|
11
|
+
export function CollectionFiltersSelector({
|
|
12
|
+
value,
|
|
13
|
+
onChange,
|
|
14
|
+
onValidityChange,
|
|
15
|
+
}: Readonly<CollectionFiltersSelectorProps>) {
|
|
11
16
|
return (
|
|
12
17
|
<div className="mt-4">
|
|
13
18
|
<ConfigurableOperationMultiSelector
|
|
@@ -18,6 +23,7 @@ export function CollectionFiltersSelector({ value, onChange }: Readonly<Collecti
|
|
|
18
23
|
dataPath="collectionFilters"
|
|
19
24
|
buttonText="Add collection filter"
|
|
20
25
|
showEnhancedDropdown={false}
|
|
26
|
+
onValidityChange={onValidityChange}
|
|
21
27
|
/>
|
|
22
28
|
</div>
|
|
23
29
|
);
|
|
@@ -141,36 +141,38 @@ export function CustomerPasswordResetVerifiedComponent(props: Readonly<HistoryEn
|
|
|
141
141
|
);
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
export function CustomerEmailUpdateComponent(
|
|
145
|
-
const { oldEmailAddress, newEmailAddress } = entry.data || {};
|
|
144
|
+
export function CustomerEmailUpdateComponent(props: Readonly<HistoryEntryProps>) {
|
|
145
|
+
const { oldEmailAddress, newEmailAddress } = props.entry.data || {};
|
|
146
146
|
|
|
147
147
|
return (
|
|
148
|
-
<
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
<
|
|
152
|
-
<
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
<
|
|
158
|
-
<
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
<
|
|
166
|
-
<
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
148
|
+
<HistoryEntry {...props}>
|
|
149
|
+
<div className="space-y-2">
|
|
150
|
+
{(oldEmailAddress || newEmailAddress) && (
|
|
151
|
+
<details className="text-xs">
|
|
152
|
+
<summary className="cursor-pointer text-muted-foreground hover:text-foreground">
|
|
153
|
+
<Trans>View details</Trans>
|
|
154
|
+
</summary>
|
|
155
|
+
<div className="mt-2 space-y-1">
|
|
156
|
+
{oldEmailAddress && (
|
|
157
|
+
<div>
|
|
158
|
+
<span className="font-medium">
|
|
159
|
+
<Trans>Old email:</Trans>
|
|
160
|
+
</span>{' '}
|
|
161
|
+
{oldEmailAddress}
|
|
162
|
+
</div>
|
|
163
|
+
)}
|
|
164
|
+
{newEmailAddress && (
|
|
165
|
+
<div>
|
|
166
|
+
<span className="font-medium">
|
|
167
|
+
<Trans>New email:</Trans>
|
|
168
|
+
</span>{' '}
|
|
169
|
+
{newEmailAddress}
|
|
170
|
+
</div>
|
|
171
|
+
)}
|
|
172
|
+
</div>
|
|
173
|
+
</details>
|
|
174
|
+
)}
|
|
175
|
+
</div>
|
|
176
|
+
</HistoryEntry>
|
|
175
177
|
);
|
|
176
178
|
}
|
|
@@ -23,7 +23,7 @@ import { Trans, useLingui } from '@lingui/react/macro';
|
|
|
23
23
|
import { createFileRoute, useNavigate } from '@tanstack/react-router';
|
|
24
24
|
import { toast } from 'sonner';
|
|
25
25
|
import { globalSettingsDocument, updateGlobalSettingsDocument } from './global-settings.graphql.js';
|
|
26
|
-
import { globalLanguageCodes } from '@/vdb/
|
|
26
|
+
import { schemaLanguageCodes as globalLanguageCodes } from '@/vdb/graphql/schema-enums.js';
|
|
27
27
|
|
|
28
28
|
const pageId = 'global-settings';
|
|
29
29
|
|
|
@@ -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
|
+
}
|