@spree/dashboard-core 6.0.1 → 6.0.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/package.json +13 -13
- package/src/components/product-stock-summary.tsx +58 -0
- package/src/components/table-toolbar.tsx +11 -14
- package/src/hooks/use-global-search.ts +1 -1
- package/src/index.ts +1 -0
- package/src/lib/search-registry.ts +1 -1
- package/src/locales/ar.json +8 -0
- package/src/locales/de.json +8 -0
- package/src/locales/en.json +8 -0
- package/src/locales/es.json +8 -0
- package/src/locales/fr.json +8 -0
- package/src/locales/ja.json +8 -0
- package/src/locales/pl.json +10 -0
- package/src/locales/vi.json +2130 -0
- package/src/locales/zh-CN.json +8 -0
- package/src/products/product-form-cards.tsx +3 -3
- package/src/products/product-form-mapping.test.ts +42 -1
- package/src/products/product-form-mapping.ts +19 -0
package/src/locales/zh-CN.json
CHANGED
|
@@ -493,6 +493,14 @@
|
|
|
493
493
|
"discard_confirm": "放弃",
|
|
494
494
|
"discard_message": "已填写的内容将全部丢失,且无法恢复。",
|
|
495
495
|
"discard_title": "放弃此草稿?"
|
|
496
|
+
},
|
|
497
|
+
"product_stock": {
|
|
498
|
+
"out_of_stock": "缺货",
|
|
499
|
+
"on_backorder": "可延期交货",
|
|
500
|
+
"in_stock": "库存 {{count}} 件",
|
|
501
|
+
"in_stock_short": "有库存",
|
|
502
|
+
"variants_one": "{{count}} 个变体",
|
|
503
|
+
"variants_other": "{{count}} 个变体"
|
|
496
504
|
}
|
|
497
505
|
},
|
|
498
506
|
"currency_amounts": {
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
useSortable,
|
|
14
14
|
} from '@dnd-kit/sortable'
|
|
15
15
|
import { CSS } from '@dnd-kit/utilities'
|
|
16
|
-
import type
|
|
16
|
+
import { type CurrencyCode, randomUUID } from '@spree/core'
|
|
17
17
|
import type { MediaType, ProductFormValues } from '@spree/dashboard-core'
|
|
18
18
|
import {
|
|
19
19
|
getApiClient,
|
|
@@ -368,7 +368,7 @@ export function MediaCard({
|
|
|
368
368
|
async (files: FileList | File[]) => {
|
|
369
369
|
const fileArray = Array.from(files)
|
|
370
370
|
for (const file of fileArray) {
|
|
371
|
-
const uploadId =
|
|
371
|
+
const uploadId = randomUUID()
|
|
372
372
|
const preview = URL.createObjectURL(file)
|
|
373
373
|
setPending((prev) => [...prev, { id: uploadId, file, preview, progress: 'uploading' }])
|
|
374
374
|
try {
|
|
@@ -445,7 +445,7 @@ export function MediaCard({
|
|
|
445
445
|
...entries.map((entry, index) => ({
|
|
446
446
|
...entry,
|
|
447
447
|
position: current.length + index + 1,
|
|
448
|
-
uploadId:
|
|
448
|
+
uploadId: randomUUID(),
|
|
449
449
|
})),
|
|
450
450
|
],
|
|
451
451
|
{ shouldDirty: true },
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
2
|
import {
|
|
3
|
+
omitUnchangedListFields,
|
|
3
4
|
prepareVariantsTaxForProductSave,
|
|
4
5
|
variantHasTaxCategoryOverride,
|
|
5
6
|
variantToFormValues,
|
|
6
7
|
variantToWirePayload,
|
|
7
8
|
} from './product-form-mapping'
|
|
8
|
-
import type { VariantFormValues } from './product-schema'
|
|
9
|
+
import type { ProductFormValues, VariantFormValues } from './product-schema'
|
|
9
10
|
import type { PanelVariant } from './product-types'
|
|
10
11
|
|
|
11
12
|
// variantToFormValues reads a handful of fields; cast a minimal fixture rather
|
|
@@ -129,3 +130,43 @@ describe('variant tax category overrides', () => {
|
|
|
129
130
|
expect(variantToWirePayload(prepared[0], 0).tax_category_id).toBeNull()
|
|
130
131
|
})
|
|
131
132
|
})
|
|
133
|
+
|
|
134
|
+
describe('omitUnchangedListFields', () => {
|
|
135
|
+
const payload = {
|
|
136
|
+
name: 'Linen shirt',
|
|
137
|
+
category_ids: ['ctg_1'],
|
|
138
|
+
collection_ids: ['coll_1'],
|
|
139
|
+
tags: ['summer'],
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
it('sends a list when its field changed', () => {
|
|
143
|
+
expect(omitUnchangedListFields(payload, { category_ids: true })).toEqual({
|
|
144
|
+
name: 'Linen shirt',
|
|
145
|
+
category_ids: ['ctg_1'],
|
|
146
|
+
})
|
|
147
|
+
expect(omitUnchangedListFields(payload, { tags: true, collection_ids: true })).toEqual({
|
|
148
|
+
name: 'Linen shirt',
|
|
149
|
+
collection_ids: ['coll_1'],
|
|
150
|
+
tags: ['summer'],
|
|
151
|
+
})
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
// React Hook Form marks an array field dirty per item when it compares item by item.
|
|
155
|
+
it('reads a per-item dirty flag', () => {
|
|
156
|
+
expect(omitUnchangedListFields(payload, { category_ids: [false, true] })).toEqual({
|
|
157
|
+
name: 'Linen shirt',
|
|
158
|
+
category_ids: ['ctg_1'],
|
|
159
|
+
})
|
|
160
|
+
expect(omitUnchangedListFields(payload, { category_ids: [false] })).toEqual({
|
|
161
|
+
name: 'Linen shirt',
|
|
162
|
+
})
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
// Another tab, a bulk action or an import may have added to a list since the
|
|
166
|
+
// page loaded; sending the loaded list back would remove those additions.
|
|
167
|
+
it('leaves the lists out when their fields did not change', () => {
|
|
168
|
+
const dirtyFields: Partial<Record<keyof ProductFormValues, boolean>> = { name: true }
|
|
169
|
+
|
|
170
|
+
expect(omitUnchangedListFields(payload, dirtyFields)).toEqual({ name: 'Linen shirt' })
|
|
171
|
+
})
|
|
172
|
+
})
|
|
@@ -195,6 +195,25 @@ export function withExtensionRowValues(
|
|
|
195
195
|
}
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
+
// The API replaces each of these lists with what a save sends.
|
|
199
|
+
const LIST_FIELDS = ['category_ids', 'collection_ids', 'tags'] as const
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* The save payload without the category, collection and tag lists the merchant
|
|
203
|
+
* left unchanged, so a save keeps what something else added since the page loaded.
|
|
204
|
+
*/
|
|
205
|
+
export function omitUnchangedListFields(
|
|
206
|
+
payload: Record<string, unknown>,
|
|
207
|
+
dirtyFields: Partial<Record<(typeof LIST_FIELDS)[number], unknown>>,
|
|
208
|
+
): Record<string, unknown> {
|
|
209
|
+
const trimmed = { ...payload }
|
|
210
|
+
for (const field of LIST_FIELDS) {
|
|
211
|
+
const dirty = dirtyFields[field]
|
|
212
|
+
if (!(Array.isArray(dirty) ? dirty.some(Boolean) : dirty)) delete trimmed[field]
|
|
213
|
+
}
|
|
214
|
+
return trimmed
|
|
215
|
+
}
|
|
216
|
+
|
|
198
217
|
// Strip UI-only fields (stock_location_name) and undefined entries so the
|
|
199
218
|
// PATCH body matches the Admin API VariantUpdateParams shape exactly. The
|
|
200
219
|
// Spree::Product#variants= setter reconciles by id, creates new entries,
|