@isoftdata/svelte-ecommerce 1.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -0
- package/dist/EcommerceCategoryMapConfiguration.svelte +266 -0
- package/dist/EcommerceCategoryMapConfiguration.svelte.d.ts +14 -0
- package/dist/EcommerceConditionMapConfiguration.svelte +214 -0
- package/dist/EcommerceConditionMapConfiguration.svelte.d.ts +10 -0
- package/dist/EcommerceConfiguration.svelte +195 -0
- package/dist/EcommerceConfiguration.svelte.d.ts +22 -0
- package/dist/EcommerceDefaults.svelte +357 -0
- package/dist/EcommerceDefaults.svelte.d.ts +12 -0
- package/dist/EcommerceListingDetails.svelte +986 -0
- package/dist/EcommerceListingDetails.svelte.d.ts +18 -0
- package/dist/EcommercePartTypeConfig.svelte +305 -0
- package/dist/EcommercePartTypeConfig.svelte.d.ts +15 -0
- package/dist/EcommerceStoreConfiguration.svelte +263 -0
- package/dist/EcommerceStoreConfiguration.svelte.d.ts +13 -0
- package/dist/PolicyList.svelte +66 -0
- package/dist/PolicyList.svelte.d.ts +10 -0
- package/dist/data/ebay.d.ts +11 -0
- package/dist/data/ebay.js +31 -0
- package/dist/data/htp.d.ts +9 -0
- package/dist/data/htp.js +22 -0
- package/dist/data/index.d.ts +4 -0
- package/dist/data/index.js +6 -0
- package/dist/data/types.d.ts +4 -0
- package/dist/data/types.js +1 -0
- package/dist/helpers/index.d.ts +3 -0
- package/dist/helpers/index.js +3 -0
- package/dist/helpers/listing.d.ts +22 -0
- package/dist/helpers/listing.js +324 -0
- package/dist/helpers/template.d.ts +31 -0
- package/dist/helpers/template.js +131 -0
- package/dist/helpers/validation.d.ts +2 -0
- package/dist/helpers/validation.js +91 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +11 -0
- package/dist/utils.d.ts +321 -0
- package/dist/utils.js +1 -0
- package/package.json +66 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { EbayCategory, EbayPolicy, EcommerceCondition, ExtendedEcommerceConditionMap, EcommercePartnerConfiguration, ExtendedEbayCategoryMap, FileItem, InventoryListingDetail, InventoryRow, InventoryType, InventoryTypeListingDefaults, NewInventoryListingDetail } from './utils.js';
|
|
2
|
+
interface Props {
|
|
3
|
+
ebayCategoryList: EbayCategory[];
|
|
4
|
+
ebayCategoryMapList: ExtendedEbayCategoryMap[];
|
|
5
|
+
ebayPolicyList: EbayPolicy[];
|
|
6
|
+
ecommerceConditionMapList: ExtendedEcommerceConditionMap[];
|
|
7
|
+
ecommerceConditionList: EcommerceCondition[];
|
|
8
|
+
ecommercePartnerConfigurationList: EcommercePartnerConfiguration[];
|
|
9
|
+
listingList: InventoryListingDetail[];
|
|
10
|
+
partFileList: FileItem[];
|
|
11
|
+
part: InventoryRow;
|
|
12
|
+
inventoryTypeWithDefaultConfigList: InventoryTypeListingDefaults[];
|
|
13
|
+
inventoryTypeList: InventoryType[];
|
|
14
|
+
save: (data: InventoryListingDetail | NewInventoryListingDetail) => Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
declare const EcommerceListingDetails: import("svelte").Component<Props, {}, "">;
|
|
17
|
+
type EcommerceListingDetails = ReturnType<typeof EcommerceListingDetails>;
|
|
18
|
+
export default EcommerceListingDetails;
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { i18n } from 'i18next'
|
|
3
|
+
|
|
4
|
+
import { getContext } from 'svelte'
|
|
5
|
+
|
|
6
|
+
import Table from '@isoftdata/svelte-table'
|
|
7
|
+
import Button from '@isoftdata/svelte-button'
|
|
8
|
+
import Select from '@isoftdata/svelte-select'
|
|
9
|
+
import Modal from '@isoftdata/svelte-modal'
|
|
10
|
+
import Checkbox from '@isoftdata/svelte-checkbox'
|
|
11
|
+
import klona from 'klona'
|
|
12
|
+
import type { InventoryType } from './utils.js'
|
|
13
|
+
import EcommerceDefaults from './EcommerceDefaults.svelte'
|
|
14
|
+
import type {
|
|
15
|
+
EbayPolicy,
|
|
16
|
+
EcommerceCondition,
|
|
17
|
+
EcommercePartnerConfiguration,
|
|
18
|
+
EcommerceSharedDefaults,
|
|
19
|
+
ExtendedInventoryTypeListingDefaults,
|
|
20
|
+
InventoryTypeCategory,
|
|
21
|
+
InventoryTypeListingDefaults,
|
|
22
|
+
NewInventoryTypeListingDefaults,
|
|
23
|
+
} from './utils.js'
|
|
24
|
+
|
|
25
|
+
import { translate as defaultTranslate } from '@isoftdata/utility-string'
|
|
26
|
+
|
|
27
|
+
const { t: translate } = getContext<i18n>('i18next') || { t: defaultTranslate }
|
|
28
|
+
|
|
29
|
+
interface Props {
|
|
30
|
+
// Props
|
|
31
|
+
ebayPolicyList: EbayPolicy[]
|
|
32
|
+
ecommerceConditionList: EcommerceCondition[]
|
|
33
|
+
inventoryTypeList: InventoryType[]
|
|
34
|
+
inventoryTypeWithDefaultConfigList: ExtendedInventoryTypeListingDefaults[]
|
|
35
|
+
inventoryTypeCategoryList: InventoryTypeCategory[]
|
|
36
|
+
partnerConfigurationList: EcommercePartnerConfiguration[]
|
|
37
|
+
selectedEcommercePartnerId: number
|
|
38
|
+
partTypeConfigurationChanged: (
|
|
39
|
+
data: InventoryTypeListingDefaults | NewInventoryTypeListingDefaults,
|
|
40
|
+
) => Promise<void>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let {
|
|
44
|
+
ebayPolicyList = [],
|
|
45
|
+
ecommerceConditionList = [],
|
|
46
|
+
inventoryTypeList = [],
|
|
47
|
+
inventoryTypeWithDefaultConfigList = $bindable([]),
|
|
48
|
+
inventoryTypeCategoryList = [],
|
|
49
|
+
partnerConfigurationList = [],
|
|
50
|
+
selectedEcommercePartnerId,
|
|
51
|
+
partTypeConfigurationChanged = async (_: InventoryTypeListingDefaults | NewInventoryTypeListingDefaults) => {},
|
|
52
|
+
}: Props = $props()
|
|
53
|
+
|
|
54
|
+
// Template for new inventory type listing configurations
|
|
55
|
+
const newInventoryTypeListingConfig: NewInventoryTypeListingDefaults = Object.freeze({
|
|
56
|
+
active: true,
|
|
57
|
+
categoryId: undefined,
|
|
58
|
+
categoryName: '',
|
|
59
|
+
defaults: {
|
|
60
|
+
brandMapping: undefined,
|
|
61
|
+
conditionDescription: undefined,
|
|
62
|
+
conditionId: undefined,
|
|
63
|
+
fulfillmentPolicies: undefined,
|
|
64
|
+
fulfillmentTimeUnit: undefined,
|
|
65
|
+
fulfillmentTimeValue: undefined,
|
|
66
|
+
listingDescriptionTemplate: undefined,
|
|
67
|
+
listingDuration: undefined,
|
|
68
|
+
listingFormat: undefined,
|
|
69
|
+
listingTitleTemplate: undefined,
|
|
70
|
+
manufacturerPartNumberMapping: undefined,
|
|
71
|
+
oemNumberMapping: undefined,
|
|
72
|
+
packageType: undefined,
|
|
73
|
+
paymentPolicies: undefined,
|
|
74
|
+
pricingModifier: undefined,
|
|
75
|
+
returnPolicies: undefined,
|
|
76
|
+
shippingLengthUnit: undefined,
|
|
77
|
+
shippingWeightUnit: undefined,
|
|
78
|
+
storePickupAllowed: undefined,
|
|
79
|
+
},
|
|
80
|
+
ecommercePartnerId: 0,
|
|
81
|
+
inventoryTypeId: 0,
|
|
82
|
+
inventoryTypeListingConfigurationId: null,
|
|
83
|
+
inventoryTypeName: '',
|
|
84
|
+
inventoryType: null,
|
|
85
|
+
descriptionTemplate: null,
|
|
86
|
+
titleTemplate: null,
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
// Local state
|
|
90
|
+
let isAddingListingConfig = false
|
|
91
|
+
let selectedInventoryTypeListingConfig: InventoryTypeListingDefaults | NewInventoryTypeListingDefaults = $state(
|
|
92
|
+
klona(newInventoryTypeListingConfig),
|
|
93
|
+
)
|
|
94
|
+
let show = $state(false)
|
|
95
|
+
let editInventoryTypeDisabled: boolean = $state(false)
|
|
96
|
+
// Hard coded temp data for drop downs
|
|
97
|
+
let showAllOption = true
|
|
98
|
+
|
|
99
|
+
// Automatically update filtered categories when inventoryType changes
|
|
100
|
+
let filteredCategories = $derived.by(() => {
|
|
101
|
+
// Use the inventoryType from the selectedInventoryTypeListingConfig if available
|
|
102
|
+
const typeToUse = selectedInventoryTypeListingConfig?.inventoryTypeId
|
|
103
|
+
|
|
104
|
+
// If no inventory type is selected, return empty array
|
|
105
|
+
if (!typeToUse) {
|
|
106
|
+
return []
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// First find any categories with a typeSetId matching the selected inventory type
|
|
110
|
+
const categoriesByInventoryType = inventoryTypeCategoryList.filter(category => category.typeSetId === typeToUse)
|
|
111
|
+
|
|
112
|
+
// Now filter out the ones that aren't already configured by the inventorytype and category combo
|
|
113
|
+
return categoriesByInventoryType.filter(category => {
|
|
114
|
+
// Check if this category is already configured for this inventory type
|
|
115
|
+
const isAlreadyConfigured = inventoryTypeWithDefaultConfigList.some(
|
|
116
|
+
config => config.inventoryTypeId === typeToUse && config.categoryId === category.categoryId,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
// Always include the currently selected category (for editing existing records)
|
|
120
|
+
const isCurrentlySelected = selectedInventoryTypeListingConfig?.categoryId === category.categoryId
|
|
121
|
+
|
|
122
|
+
// Return categories that aren't already configured OR are currently selected
|
|
123
|
+
return !isAlreadyConfigured || isCurrentlySelected
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
// Called on add or edit inventory type config
|
|
128
|
+
function editInventoryTypeConfig(row?: InventoryTypeListingDefaults) {
|
|
129
|
+
if (row) {
|
|
130
|
+
// Editing existing row - clone the provided row
|
|
131
|
+
selectedInventoryTypeListingConfig = klona(row)
|
|
132
|
+
editInventoryTypeDisabled = true
|
|
133
|
+
} else {
|
|
134
|
+
// Adding new row - ensure completely fresh copy from helper function
|
|
135
|
+
selectedInventoryTypeListingConfig = newInventoryTypeListingConfig
|
|
136
|
+
editInventoryTypeDisabled = false
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
show = true
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Function to prepare data and call for saving
|
|
143
|
+
async function confirm() {
|
|
144
|
+
if (!selectedInventoryTypeListingConfig) return
|
|
145
|
+
|
|
146
|
+
// Create a copy of the data to avoid mutating state directly
|
|
147
|
+
const listingConfigCopy = klona(selectedInventoryTypeListingConfig)
|
|
148
|
+
|
|
149
|
+
// // TS hates this being possibly null
|
|
150
|
+
// if (!selectedInventoryTypeListingConfig.inventoryTypeId) {
|
|
151
|
+
// // Show error message or return early
|
|
152
|
+
// console.error('Inventory Type is required')
|
|
153
|
+
// return
|
|
154
|
+
// }
|
|
155
|
+
|
|
156
|
+
// Now create the object we want to save since there are some extras
|
|
157
|
+
|
|
158
|
+
// Call the parent save function with prepared data
|
|
159
|
+
await partTypeConfigurationChanged(listingConfigCopy)
|
|
160
|
+
|
|
161
|
+
// Close the modal after saving
|
|
162
|
+
close()
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function close() {
|
|
166
|
+
// Reset to a completely fresh template to prevent any state contamination
|
|
167
|
+
selectedInventoryTypeListingConfig = newInventoryTypeListingConfig
|
|
168
|
+
editInventoryTypeDisabled = false
|
|
169
|
+
show = false
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function handlePartTypeDefaultsChange(newDefaults: EcommerceSharedDefaults) {
|
|
173
|
+
// This function is called when the EcommerceDefaults component changes
|
|
174
|
+
// The binding will automatically update selectedStoreConfigRow.defaults
|
|
175
|
+
// Don't need to do anything here since we're not auto-saving, feels dumb having an empty method
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function isRowSelected(row: ExtendedInventoryTypeListingDefaults): boolean {
|
|
179
|
+
if (!selectedInventoryTypeListingConfig) return false
|
|
180
|
+
|
|
181
|
+
// For existing records, compare IDs
|
|
182
|
+
if ('inventoryTypeListingConfigurationId' in selectedInventoryTypeListingConfig) {
|
|
183
|
+
return (
|
|
184
|
+
selectedInventoryTypeListingConfig.inventoryTypeListingConfigurationId ===
|
|
185
|
+
row.inventoryTypeListingConfigurationId
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// For new records, no row can be selected since they don't exist in the table yet
|
|
190
|
+
return false
|
|
191
|
+
}
|
|
192
|
+
</script>
|
|
193
|
+
|
|
194
|
+
<div class="card-header">
|
|
195
|
+
<h4 class="mb-0">
|
|
196
|
+
{translate('ecommerce.configuration.partTypeListingConfiguration', 'Part Type Listing Configuration')}
|
|
197
|
+
</h4>
|
|
198
|
+
<p class="text-muted mb-0">
|
|
199
|
+
{translate(
|
|
200
|
+
'ecommerce.configuration.configurePartTypeCategory',
|
|
201
|
+
'Configure part type and category level settings for e-commerce listings',
|
|
202
|
+
)}
|
|
203
|
+
</p>
|
|
204
|
+
</div>
|
|
205
|
+
<div class="card-body">
|
|
206
|
+
<div class="mb-3">
|
|
207
|
+
<Button
|
|
208
|
+
outline
|
|
209
|
+
color="success"
|
|
210
|
+
size="sm"
|
|
211
|
+
iconClass="plus"
|
|
212
|
+
onclick={() => editInventoryTypeConfig()}
|
|
213
|
+
disabled={isAddingListingConfig}
|
|
214
|
+
>
|
|
215
|
+
{translate('ecommerce.configuration.add', 'Add')}
|
|
216
|
+
</Button>
|
|
217
|
+
</div>
|
|
218
|
+
<Table
|
|
219
|
+
rows={inventoryTypeWithDefaultConfigList}
|
|
220
|
+
columns={[
|
|
221
|
+
{
|
|
222
|
+
property: 'inventoryTypeId',
|
|
223
|
+
name: translate('ecommerce.configuration.typeNumber', 'Type #'),
|
|
224
|
+
defaultSortColumn: true,
|
|
225
|
+
},
|
|
226
|
+
{ property: 'inventoryTypeName', name: translate('ecommerce.configuration.name', 'Name') },
|
|
227
|
+
{ property: 'categoryName', name: translate('ecommerce.configuration.category', 'Category') },
|
|
228
|
+
{ property: 'active', name: translate('ecommerce.configuration.activeOnEbay', 'Active on eBay') },
|
|
229
|
+
]}
|
|
230
|
+
>
|
|
231
|
+
{#snippet children({ row })}
|
|
232
|
+
<tr
|
|
233
|
+
onclick={() => editInventoryTypeConfig(row)}
|
|
234
|
+
class="cursor-pointer"
|
|
235
|
+
class:table-primary={isRowSelected(row)}
|
|
236
|
+
>
|
|
237
|
+
<td>{row.inventoryTypeId}</td>
|
|
238
|
+
<td>{row.inventoryTypeName}</td>
|
|
239
|
+
<td>{row.categoryName}</td>
|
|
240
|
+
<td
|
|
241
|
+
>{row.active
|
|
242
|
+
? translate('ecommerce.configuration.yes', 'yes')
|
|
243
|
+
: translate('ecommerce.configuration.no', 'No')}</td
|
|
244
|
+
>
|
|
245
|
+
</tr>
|
|
246
|
+
{/snippet}
|
|
247
|
+
</Table>
|
|
248
|
+
</div>
|
|
249
|
+
<Modal
|
|
250
|
+
bind:show
|
|
251
|
+
title={translate('ecommerce.configuration.addEditPartTypeCategory', 'Add/Edit Part Type & Category Configuration')}
|
|
252
|
+
modalSize="lg"
|
|
253
|
+
confirmButtonText={translate('ecommerce.configuration.save', 'Save')}
|
|
254
|
+
cancelShown={true}
|
|
255
|
+
{confirm}
|
|
256
|
+
{close}
|
|
257
|
+
>
|
|
258
|
+
<Checkbox
|
|
259
|
+
label={translate('ecommerce.configuration.activeOnEbay', 'Active On eBay')}
|
|
260
|
+
bind:checked={selectedInventoryTypeListingConfig.active}
|
|
261
|
+
onchange={() => {
|
|
262
|
+
// Ensure active is always a boolean, not a string
|
|
263
|
+
selectedInventoryTypeListingConfig.active = !!selectedInventoryTypeListingConfig.active
|
|
264
|
+
}}
|
|
265
|
+
/>
|
|
266
|
+
<div class="row">
|
|
267
|
+
<div class="col-md-3">
|
|
268
|
+
<Select
|
|
269
|
+
label={translate('ecommerce.configuration.partType', 'Part Type')}
|
|
270
|
+
showEmptyOption={showAllOption}
|
|
271
|
+
emptyValue={null}
|
|
272
|
+
emptyText={translate('ecommerce.configuration.selectType', 'Select Type')}
|
|
273
|
+
disabled={editInventoryTypeDisabled}
|
|
274
|
+
bind:value={selectedInventoryTypeListingConfig.inventoryTypeId}
|
|
275
|
+
>
|
|
276
|
+
{#each inventoryTypeList as type}
|
|
277
|
+
<option value={type.inventoryTypeId}>{type.inventoryTypeId} - {type.name}</option>
|
|
278
|
+
{/each}
|
|
279
|
+
</Select>
|
|
280
|
+
</div>
|
|
281
|
+
<div class="col-md-3">
|
|
282
|
+
<Select
|
|
283
|
+
label={translate('ecommerce.configuration.category', 'Category')}
|
|
284
|
+
showEmptyOption={showAllOption}
|
|
285
|
+
emptyValue={null}
|
|
286
|
+
emptyText={translate('ecommerce.configuration.selectCategory', 'Selectd Category')}
|
|
287
|
+
disabled={!filteredCategories.length && !selectedInventoryTypeListingConfig.categoryId}
|
|
288
|
+
bind:value={selectedInventoryTypeListingConfig.categoryId}
|
|
289
|
+
>
|
|
290
|
+
{#each filteredCategories as category}
|
|
291
|
+
<option value={category.categoryId}>{category.name}</option>
|
|
292
|
+
{/each}
|
|
293
|
+
</Select>
|
|
294
|
+
</div>
|
|
295
|
+
</div>
|
|
296
|
+
<hr />
|
|
297
|
+
<EcommerceDefaults
|
|
298
|
+
defaults={selectedInventoryTypeListingConfig.defaults}
|
|
299
|
+
defaultsChanged={handlePartTypeDefaultsChange}
|
|
300
|
+
{ebayPolicyList}
|
|
301
|
+
{ecommerceConditionList}
|
|
302
|
+
{partnerConfigurationList}
|
|
303
|
+
{selectedEcommercePartnerId}
|
|
304
|
+
/>
|
|
305
|
+
</Modal>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { InventoryType } from './utils.js';
|
|
2
|
+
import type { EbayPolicy, EcommerceCondition, EcommercePartnerConfiguration, ExtendedInventoryTypeListingDefaults, InventoryTypeCategory, InventoryTypeListingDefaults, NewInventoryTypeListingDefaults } from './utils.js';
|
|
3
|
+
interface Props {
|
|
4
|
+
ebayPolicyList: EbayPolicy[];
|
|
5
|
+
ecommerceConditionList: EcommerceCondition[];
|
|
6
|
+
inventoryTypeList: InventoryType[];
|
|
7
|
+
inventoryTypeWithDefaultConfigList: ExtendedInventoryTypeListingDefaults[];
|
|
8
|
+
inventoryTypeCategoryList: InventoryTypeCategory[];
|
|
9
|
+
partnerConfigurationList: EcommercePartnerConfiguration[];
|
|
10
|
+
selectedEcommercePartnerId: number;
|
|
11
|
+
partTypeConfigurationChanged: (data: InventoryTypeListingDefaults | NewInventoryTypeListingDefaults) => Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
declare const EcommercePartTypeConfig: import("svelte").Component<Props, {}, "inventoryTypeWithDefaultConfigList">;
|
|
14
|
+
type EcommercePartTypeConfig = ReturnType<typeof EcommercePartTypeConfig>;
|
|
15
|
+
export default EcommercePartTypeConfig;
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { i18n } from 'i18next'
|
|
3
|
+
|
|
4
|
+
import { getContext } from 'svelte'
|
|
5
|
+
import Table from '@isoftdata/svelte-table'
|
|
6
|
+
import klona from 'klona'
|
|
7
|
+
import Modal from '@isoftdata/svelte-modal'
|
|
8
|
+
import Checkbox from '@isoftdata/svelte-checkbox'
|
|
9
|
+
import Button from '@isoftdata/svelte-button'
|
|
10
|
+
import Select from '@isoftdata/svelte-select'
|
|
11
|
+
import type {
|
|
12
|
+
EbayLocation,
|
|
13
|
+
EbayPolicy,
|
|
14
|
+
EcommerceCondition,
|
|
15
|
+
EcommercePartnerConfiguration,
|
|
16
|
+
EcommerceStoreConfig,
|
|
17
|
+
Store,
|
|
18
|
+
EcommerceSharedDefaults,
|
|
19
|
+
} from './utils.js'
|
|
20
|
+
import EcommerceDefaults from './EcommerceDefaults.svelte'
|
|
21
|
+
|
|
22
|
+
import { translate as defaultTranslate } from '@isoftdata/utility-string'
|
|
23
|
+
const { t: translate } = getContext<i18n>('i18next') || { t: defaultTranslate }
|
|
24
|
+
|
|
25
|
+
interface Props {
|
|
26
|
+
ebayLocationList?: EbayLocation[]
|
|
27
|
+
ebayPolicyList?: EbayPolicy[]
|
|
28
|
+
ecommerceConditionList: EcommerceCondition[]
|
|
29
|
+
partnerConfigurationList?: EcommercePartnerConfiguration[]
|
|
30
|
+
selectedEcommercePartnerId: number
|
|
31
|
+
storeList: Store[]
|
|
32
|
+
partnerConfigurationChanged: (partnerConfig: EcommercePartnerConfiguration) => Promise<void>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let {
|
|
36
|
+
ebayLocationList = [],
|
|
37
|
+
ebayPolicyList = [],
|
|
38
|
+
ecommerceConditionList = [],
|
|
39
|
+
partnerConfigurationList = [],
|
|
40
|
+
selectedEcommercePartnerId,
|
|
41
|
+
storeList = [],
|
|
42
|
+
partnerConfigurationChanged = async (_: EcommercePartnerConfiguration) => {},
|
|
43
|
+
}: Props = $props()
|
|
44
|
+
|
|
45
|
+
let newStoreConfig: EcommerceStoreConfig = Object.freeze({
|
|
46
|
+
active: true,
|
|
47
|
+
defaults: {
|
|
48
|
+
brandMapping: undefined,
|
|
49
|
+
conditionDescription: undefined,
|
|
50
|
+
conditionId: undefined,
|
|
51
|
+
fulfillmentPolicies: undefined,
|
|
52
|
+
fulfillmentTimeUnit: undefined,
|
|
53
|
+
fulfillmentTimeValue: undefined,
|
|
54
|
+
listingDescriptionTemplate: undefined,
|
|
55
|
+
listingDuration: undefined,
|
|
56
|
+
listingFormat: undefined,
|
|
57
|
+
listingTitleTemplate: undefined,
|
|
58
|
+
manufacturerPartNumberMapping: undefined,
|
|
59
|
+
oemNumberMapping: undefined,
|
|
60
|
+
packageType: undefined,
|
|
61
|
+
paymentPolicies: undefined,
|
|
62
|
+
pricingModifier: undefined,
|
|
63
|
+
returnPolicies: undefined,
|
|
64
|
+
shippingLengthUnit: undefined,
|
|
65
|
+
shippingWeightUnit: undefined,
|
|
66
|
+
storePickupAllowed: undefined,
|
|
67
|
+
},
|
|
68
|
+
merchantLocationKey: undefined,
|
|
69
|
+
name: '',
|
|
70
|
+
storeId: 0,
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
let show = $state(false)
|
|
74
|
+
let showAllOption = $state(true)
|
|
75
|
+
let selectedStoreConfigRow = $state<EcommerceStoreConfig | null>(null)
|
|
76
|
+
|
|
77
|
+
// If we had more than one ecommercepartner, this would change depending on selected partner id
|
|
78
|
+
let selectedPartnerConfig = $derived(
|
|
79
|
+
partnerConfigurationList.find(row => row.ecommercePartnerId === selectedEcommercePartnerId),
|
|
80
|
+
)
|
|
81
|
+
// Get the store rows from the partnerconfig row
|
|
82
|
+
let selectedPartnerConfigStoreRows = $derived(selectedPartnerConfig?.defaults?.store || [])
|
|
83
|
+
|
|
84
|
+
// Filter available ebay locations depending on whether we're adding or editing
|
|
85
|
+
let availableLocations = $derived.by(() => {
|
|
86
|
+
const configuredEbayLocations = selectedPartnerConfigStoreRows.map(row => row.merchantLocationKey)
|
|
87
|
+
const isEditingExisting =
|
|
88
|
+
selectedStoreConfigRow?.name !== null &&
|
|
89
|
+
configuredEbayLocations.includes(selectedStoreConfigRow?.merchantLocationKey)
|
|
90
|
+
|
|
91
|
+
if (isEditingExisting) {
|
|
92
|
+
return ebayLocationList
|
|
93
|
+
}
|
|
94
|
+
return ebayLocationList.filter(location => !configuredEbayLocations.includes(location.ebayName))
|
|
95
|
+
})
|
|
96
|
+
// Filter available stores based on whether we're adding or editing existing
|
|
97
|
+
let availableStores = $derived.by(() => {
|
|
98
|
+
if (!selectedStoreConfigRow) return storeList
|
|
99
|
+
|
|
100
|
+
// Get the IDs of already configured stores
|
|
101
|
+
const configuredStoreIds = selectedPartnerConfigStoreRows.map(row => row.storeId)
|
|
102
|
+
const isEditingExisting =
|
|
103
|
+
selectedStoreConfigRow.storeId !== null && configuredStoreIds.includes(selectedStoreConfigRow.storeId)
|
|
104
|
+
|
|
105
|
+
if (isEditingExisting) {
|
|
106
|
+
return storeList
|
|
107
|
+
}
|
|
108
|
+
// If adding new, filter out already configured stores
|
|
109
|
+
return storeList.filter(store => !configuredStoreIds.includes(store.storeId))
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
async function confirm() {
|
|
113
|
+
if (!selectedStoreConfigRow || !selectedStoreConfigRow.storeId) return
|
|
114
|
+
|
|
115
|
+
// TODO: Here I am regretting my decision to have JSON sub object business for this
|
|
116
|
+
// Copy the store config data for uh state wackiness reasons?
|
|
117
|
+
const storeConfigRowCopy = klona(selectedStoreConfigRow)
|
|
118
|
+
// Copy the partner config row
|
|
119
|
+
const partnerConfigCopy = klona(selectedPartnerConfig)
|
|
120
|
+
|
|
121
|
+
// Copy the selectedParnterConfigStoreRows
|
|
122
|
+
let updatedStoreRows = klona(selectedPartnerConfigStoreRows || [])
|
|
123
|
+
|
|
124
|
+
// If empty, add storeConfigCopy
|
|
125
|
+
if (updatedStoreRows.length === 0) {
|
|
126
|
+
updatedStoreRows.push(storeConfigRowCopy)
|
|
127
|
+
} else {
|
|
128
|
+
const existingIndex = updatedStoreRows.findIndex(row => row.storeId === storeConfigRowCopy.storeId)
|
|
129
|
+
if (existingIndex === -1) {
|
|
130
|
+
updatedStoreRows.push(storeConfigRowCopy)
|
|
131
|
+
} else {
|
|
132
|
+
updatedStoreRows[existingIndex] = storeConfigRowCopy
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// Still possible to be missing defaults?
|
|
136
|
+
if (partnerConfigCopy && partnerConfigCopy.defaults) {
|
|
137
|
+
partnerConfigCopy.defaults.store = updatedStoreRows
|
|
138
|
+
await partnerConfigurationChanged(partnerConfigCopy)
|
|
139
|
+
}
|
|
140
|
+
close()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function close() {
|
|
144
|
+
selectedStoreConfigRow = newStoreConfig
|
|
145
|
+
show = false
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function editStoreConfigRow(row?: EcommerceStoreConfig) {
|
|
149
|
+
// Set selected row
|
|
150
|
+
selectedStoreConfigRow = klona(row || newStoreConfig)
|
|
151
|
+
|
|
152
|
+
show = true
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function handleStoreDefaultsChange(newDefaults: EcommerceSharedDefaults) {
|
|
156
|
+
// This function is called when the EcommerceDefaults component changes
|
|
157
|
+
// The binding will automatically update selectedStoreConfigRow.defaults
|
|
158
|
+
// Don't need to do anything here since we're not auto-saving, feels dumb having an empty method
|
|
159
|
+
}
|
|
160
|
+
</script>
|
|
161
|
+
|
|
162
|
+
<div class="card-header">
|
|
163
|
+
<h4 class="mb-0">{translate('ecommerce.configuration.storeConfiguration', 'Store Configuration')}</h4>
|
|
164
|
+
<p class="text-muted mb-0">
|
|
165
|
+
{translate('ecommerce.configuration.configureStoreLevelEbaySettings', 'Configure store level eBay settings')}
|
|
166
|
+
</p>
|
|
167
|
+
</div>
|
|
168
|
+
<div class="card-body">
|
|
169
|
+
<div class="mb-3">
|
|
170
|
+
<Button
|
|
171
|
+
outline
|
|
172
|
+
color="success"
|
|
173
|
+
size="sm"
|
|
174
|
+
iconClass="plus"
|
|
175
|
+
onclick={() => editStoreConfigRow()}
|
|
176
|
+
disabled={false}
|
|
177
|
+
>
|
|
178
|
+
{translate('ecommerce.configuration.add', 'Add')}
|
|
179
|
+
</Button>
|
|
180
|
+
</div>
|
|
181
|
+
{#if selectedPartnerConfigStoreRows.length}
|
|
182
|
+
<Table
|
|
183
|
+
rows={selectedPartnerConfigStoreRows}
|
|
184
|
+
responsive
|
|
185
|
+
stickyHeader
|
|
186
|
+
parentClass="overflow-y-auto mh-400"
|
|
187
|
+
columns={[
|
|
188
|
+
{ property: 'storeId', name: translate('ecommerce.configuration.storeId', 'Store ID') },
|
|
189
|
+
{ property: 'name', name: translate('ecommerce.configuration.storeName', 'Store Name') },
|
|
190
|
+
{ property: 'merchantLocationKey', name: translate('ecommerce.configuration.ebayLocation', 'Ebay Location') },
|
|
191
|
+
{ property: 'active', name: translate('ecommerce.configuration.active', 'Active') },
|
|
192
|
+
]}
|
|
193
|
+
>
|
|
194
|
+
{#snippet children({ row })}
|
|
195
|
+
<tr
|
|
196
|
+
onclick={() => editStoreConfigRow(row as EcommerceStoreConfig)}
|
|
197
|
+
class="cursor-pointer"
|
|
198
|
+
>
|
|
199
|
+
<td property="storeId">{row.storeId}</td>
|
|
200
|
+
<td property="name">{row.name}</td>
|
|
201
|
+
<td property="merchantLocationKey">{row.merchantLocationKey}</td>
|
|
202
|
+
<td property="active">{row.active}</td>
|
|
203
|
+
</tr>
|
|
204
|
+
{/snippet}
|
|
205
|
+
</Table>
|
|
206
|
+
{/if}
|
|
207
|
+
</div>
|
|
208
|
+
|
|
209
|
+
<Modal
|
|
210
|
+
bind:show
|
|
211
|
+
title={translate('ecommerce.configuration.addEditStoreDefaults', 'Add/Edit Store Defaults')}
|
|
212
|
+
modalSize="lg"
|
|
213
|
+
confirmButtonText={translate('ecommerce.configuration.save', 'Save')}
|
|
214
|
+
cancelShown={true}
|
|
215
|
+
{confirm}
|
|
216
|
+
{close}
|
|
217
|
+
>
|
|
218
|
+
{#if selectedStoreConfigRow}
|
|
219
|
+
<div class="card-body">
|
|
220
|
+
<Checkbox
|
|
221
|
+
label={translate('ecommerce.configuration.activeOnEbay', 'Active on eBay')}
|
|
222
|
+
bind:checked={selectedStoreConfigRow.active}
|
|
223
|
+
/>
|
|
224
|
+
|
|
225
|
+
<Select
|
|
226
|
+
label={translate('ecommerce.configuration.store', 'Store')}
|
|
227
|
+
showEmptyOption={showAllOption}
|
|
228
|
+
emptyValue={null}
|
|
229
|
+
emptyText={translate('ecommerce.configuration.selectStore', 'Select Store')}
|
|
230
|
+
disabled={false}
|
|
231
|
+
bind:value={selectedStoreConfigRow.storeId}
|
|
232
|
+
>
|
|
233
|
+
{#each availableStores as store}
|
|
234
|
+
<option value={store.storeId}>{store.name}</option>
|
|
235
|
+
{/each}
|
|
236
|
+
</Select>
|
|
237
|
+
{#if selectedStoreConfigRow.storeId}
|
|
238
|
+
<Select
|
|
239
|
+
label={translate('ecommerce.configuration.ebayLocation', 'Ebay Location')}
|
|
240
|
+
showEmptyOption={showAllOption}
|
|
241
|
+
emptyValue={null}
|
|
242
|
+
emptyText={translate('ecommerce.configuration.selectLocation', 'Select Location')}
|
|
243
|
+
disabled={false}
|
|
244
|
+
bind:value={selectedStoreConfigRow.merchantLocationKey}
|
|
245
|
+
>
|
|
246
|
+
{#each availableLocations as location}
|
|
247
|
+
<option value={location.ebayName}>{location.ebayName}</option>
|
|
248
|
+
{/each}
|
|
249
|
+
</Select>
|
|
250
|
+
{/if}
|
|
251
|
+
{#if selectedStoreConfigRow.merchantLocationKey}
|
|
252
|
+
<EcommerceDefaults
|
|
253
|
+
bind:defaults={selectedStoreConfigRow.defaults}
|
|
254
|
+
{ebayPolicyList}
|
|
255
|
+
{ecommerceConditionList}
|
|
256
|
+
{partnerConfigurationList}
|
|
257
|
+
defaultsChanged={handleStoreDefaultsChange}
|
|
258
|
+
{selectedEcommercePartnerId}
|
|
259
|
+
/>
|
|
260
|
+
{/if}
|
|
261
|
+
</div>
|
|
262
|
+
{/if}
|
|
263
|
+
</Modal>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { EbayLocation, EbayPolicy, EcommerceCondition, EcommercePartnerConfiguration, Store } from './utils.js';
|
|
2
|
+
interface Props {
|
|
3
|
+
ebayLocationList?: EbayLocation[];
|
|
4
|
+
ebayPolicyList?: EbayPolicy[];
|
|
5
|
+
ecommerceConditionList: EcommerceCondition[];
|
|
6
|
+
partnerConfigurationList?: EcommercePartnerConfiguration[];
|
|
7
|
+
selectedEcommercePartnerId: number;
|
|
8
|
+
storeList: Store[];
|
|
9
|
+
partnerConfigurationChanged: (partnerConfig: EcommercePartnerConfiguration) => Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
declare const EcommerceStoreConfiguration: import("svelte").Component<Props, {}, "">;
|
|
12
|
+
type EcommerceStoreConfiguration = ReturnType<typeof EcommerceStoreConfiguration>;
|
|
13
|
+
export default EcommerceStoreConfiguration;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { i18n } from 'i18next'
|
|
3
|
+
|
|
4
|
+
import { getContext } from 'svelte'
|
|
5
|
+
|
|
6
|
+
import Table, { Td } from '@isoftdata/svelte-table'
|
|
7
|
+
import Checkbox from '@isoftdata/svelte-checkbox'
|
|
8
|
+
import { translate as defaultTranslate } from '@isoftdata/utility-string'
|
|
9
|
+
|
|
10
|
+
const { t: translate } = getContext<i18n>('i18next') || { t: defaultTranslate }
|
|
11
|
+
|
|
12
|
+
import type { PolicyRowWithChecked } from './utils.js'
|
|
13
|
+
|
|
14
|
+
interface Props {
|
|
15
|
+
title: string
|
|
16
|
+
policies: PolicyRowWithChecked[]
|
|
17
|
+
selectedPolicyIds: string[]
|
|
18
|
+
onSelectionChange: (selectedIds: string[]) => void
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let { title, policies, selectedPolicyIds, onSelectionChange }: Props = $props()
|
|
22
|
+
|
|
23
|
+
function handleCheckboxChange(policy: PolicyRowWithChecked, checked: boolean) {
|
|
24
|
+
let updatedIds: string[]
|
|
25
|
+
|
|
26
|
+
if (checked) {
|
|
27
|
+
// Add policy to the list if not already present
|
|
28
|
+
if (!selectedPolicyIds.includes(policy.policyId)) {
|
|
29
|
+
updatedIds = [...selectedPolicyIds, policy.policyId]
|
|
30
|
+
} else {
|
|
31
|
+
updatedIds = selectedPolicyIds
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
// Remove policy from the list
|
|
35
|
+
updatedIds = selectedPolicyIds.filter(id => id !== policy.policyId)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
onSelectionChange(updatedIds)
|
|
39
|
+
}
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<div class="mb-4">
|
|
43
|
+
<Table
|
|
44
|
+
responsive
|
|
45
|
+
{title}
|
|
46
|
+
stickyHeader
|
|
47
|
+
rows={policies}
|
|
48
|
+
parentClass="overflow-y-auto mh-500"
|
|
49
|
+
columns={[
|
|
50
|
+
{ property: 'checked', name: translate('configuration.ecommerce.selected', 'Selected'), width: '100px' },
|
|
51
|
+
{ property: 'name', name: translate('configuration.ecommerce.policy', 'Policy'), defaultSortColumn: true },
|
|
52
|
+
]}
|
|
53
|
+
>
|
|
54
|
+
{#snippet children({ row })}
|
|
55
|
+
<tr>
|
|
56
|
+
<Td property="checked">
|
|
57
|
+
<Checkbox
|
|
58
|
+
checked={row.checked}
|
|
59
|
+
onchange={e => handleCheckboxChange(row, e.currentTarget.checked)}
|
|
60
|
+
/>
|
|
61
|
+
</Td>
|
|
62
|
+
<td>{row.name}</td>
|
|
63
|
+
</tr>
|
|
64
|
+
{/snippet}
|
|
65
|
+
</Table>
|
|
66
|
+
</div>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PolicyRowWithChecked } from './utils.js';
|
|
2
|
+
interface Props {
|
|
3
|
+
title: string;
|
|
4
|
+
policies: PolicyRowWithChecked[];
|
|
5
|
+
selectedPolicyIds: string[];
|
|
6
|
+
onSelectionChange: (selectedIds: string[]) => void;
|
|
7
|
+
}
|
|
8
|
+
declare const PolicyList: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type PolicyList = ReturnType<typeof PolicyList>;
|
|
10
|
+
export default PolicyList;
|