@oxyshop/admin 1.3.35 → 1.3.39

Sign up to get free protection for your applications and to get access to all the features.
package/lib/index.js CHANGED
@@ -25,6 +25,9 @@ import 'semantic-ui-css/components/visibility';
25
25
  import 'semantic-ui-css/components/visit';
26
26
  import 'jquery.dirtyforms/jquery.dirtyforms';
27
27
  import 'chart.js/dist/Chart.min';
28
+ import Chart$1 from 'chart.js';
29
+ import { axios } from '@oxyshop/shop/lib/plugins/Axios';
30
+ import axios$1 from 'axios';
28
31
  import 'semantic-ui-css/semantic.css';
29
32
  import '@oxyshop/admin/scss/main.scss';
30
33
  import '@oxyshop/admin/lib/style.css';
@@ -1884,11 +1887,13 @@ $.fn.extend({
1884
1887
  },
1885
1888
  });
1886
1889
 
1887
- $.fn.extend({
1888
- feedCategorySelect() {
1889
- const matchedElements = this;
1890
+ class FeedCategorySelect {
1891
+ constructor(selector) {
1892
+ this.selectSelector = selector;
1893
+ }
1890
1894
 
1891
- Object.values(matchedElements).forEach((selectElement) => {
1895
+ init() {
1896
+ document.querySelectorAll(this.selectSelector).forEach((selectElement) => {
1892
1897
  const sourceCode = selectElement.getAttribute('data-source-code');
1893
1898
 
1894
1899
  $(selectElement).dropdown({
@@ -1899,8 +1904,154 @@ $.fn.extend({
1899
1904
  minCharacters: 2,
1900
1905
  });
1901
1906
  });
1902
- },
1903
- });
1907
+ }
1908
+ }
1909
+
1910
+ class ProductVariantPricingGraph {
1911
+ constructor(containerSelector) {
1912
+ this.containerSelector = containerSelector;
1913
+ }
1914
+
1915
+ init() {
1916
+ document.querySelectorAll(this.containerSelector).forEach((componentContainer) => {
1917
+ const graphsData = JSON.parse(componentContainer.getAttribute('data-graph'));
1918
+
1919
+ for (const currencyGraphIndex in graphsData) {
1920
+ const currencyGraph = graphsData[currencyGraphIndex];
1921
+
1922
+ const currencyCodeHeader = document.createElement('h4');
1923
+ currencyCodeHeader.classList.add('ui', 'header');
1924
+ currencyCodeHeader.textContent = currencyGraph.currency;
1925
+
1926
+ const graphCanvas = document.createElement('canvas');
1927
+ graphCanvas.width = 400;
1928
+ graphCanvas.height = 200;
1929
+
1930
+ const graphContainer = document.createElement('div');
1931
+ graphContainer.append(currencyCodeHeader);
1932
+ graphContainer.append(graphCanvas);
1933
+
1934
+ componentContainer.append(graphContainer);
1935
+
1936
+ const graphConfig = this.getGraphConfig(currencyGraph.graph);
1937
+ new Chart$1(graphCanvas.getContext('2d'), graphConfig);
1938
+ }
1939
+ });
1940
+ }
1941
+
1942
+ /** @private */
1943
+ getGraphConfig(rawGraphData) {
1944
+ return {
1945
+ type: 'line',
1946
+ data: {
1947
+ datasets: rawGraphData.datasets.map((rawDataset) => {
1948
+ return {
1949
+ label: rawDataset.label,
1950
+ data: rawDataset.data,
1951
+ borderColor: this.getRandomRgbColor(),
1952
+ fill: false,
1953
+ steppedLine: true,
1954
+ pointRadius: 8,
1955
+ pointHoverRadius: 10,
1956
+ }
1957
+ }),
1958
+ },
1959
+ options: {
1960
+ maintainAspectRatio: true,
1961
+ responsive: true,
1962
+ scales: {
1963
+ xAxes: [
1964
+ {
1965
+ type: 'linear',
1966
+ position: 'bottom',
1967
+ scaleLabel: {
1968
+ display: true,
1969
+ labelString: rawGraphData.xAxisLabel,
1970
+ },
1971
+ },
1972
+ ],
1973
+ yAxes: [
1974
+ {
1975
+ scaleLabel: {
1976
+ display: true,
1977
+ labelString: rawGraphData.yAxisLabel,
1978
+ },
1979
+ },
1980
+ ],
1981
+ },
1982
+ },
1983
+ }
1984
+ }
1985
+
1986
+ /** @private */
1987
+ getRandomRgbColor() {
1988
+ const r = Math.floor(Math.random() * 255);
1989
+ const g = Math.floor(Math.random() * 255);
1990
+ const b = Math.floor(Math.random() * 255);
1991
+ return `rgb(${r},${g},${b})`
1992
+ }
1993
+ }
1994
+
1995
+ class CustomerGroupClientAssigner {
1996
+ constructor(selector, searchSelector = '.ng-client-search-select', submitSelector = '.ng-client-assign-submit') {
1997
+ this.selector = selector;
1998
+ this.searchSelector = searchSelector;
1999
+ this.submitSelector = submitSelector;
2000
+
2001
+ this.containerElement = null;
2002
+ this.searchElement = null;
2003
+ this.submitElement = null;
2004
+ }
2005
+
2006
+ init() {
2007
+ this.containerElement = document.querySelector(this.selector);
2008
+ if (null === this.containerElement) {
2009
+ return
2010
+ }
2011
+
2012
+ const submitApiEndpoint = this.containerElement.getAttribute('data-assign-customers-url');
2013
+ const redirectAfterUrl = this.containerElement.getAttribute('data-redirect-after-url');
2014
+
2015
+ this.searchElement = this.containerElement.querySelector(this.searchSelector);
2016
+ const searchApiEndpoint = this.searchElement.getAttribute('data-customer-search-url');
2017
+
2018
+ $(this.searchElement).dropdown({
2019
+ apiSettings: {
2020
+ url: `${searchApiEndpoint}/{query}`,
2021
+ cache: false,
2022
+ },
2023
+
2024
+ minCharacters: 2,
2025
+ });
2026
+
2027
+ this.submitElement = this.containerElement.querySelector(this.submitSelector);
2028
+ this.submitElement.addEventListener('click', () => {
2029
+ const selectedCustomersIds = this.getSelectValues(this.searchElement);
2030
+ this.submitCustomers(submitApiEndpoint, selectedCustomersIds, redirectAfterUrl);
2031
+ });
2032
+ }
2033
+
2034
+ getSelectValues(selectElement) {
2035
+ const selected = selectElement.querySelectorAll('option:checked');
2036
+ return Array.from(selected).map((optionElement) => optionElement.value)
2037
+ }
2038
+
2039
+ submitCustomers(apiEndpoint, selectedIds, redirectTo) {
2040
+ this.submitElement.disabled = true;
2041
+
2042
+ axios
2043
+ .post(apiEndpoint, { customers: selectedIds })
2044
+ .then(() => {
2045
+ window.location = redirectTo;
2046
+ })
2047
+ .catch((error) => {
2048
+ console.error(error);
2049
+ })
2050
+ .finally(() => {
2051
+ this.submitElement.disabled = false;
2052
+ });
2053
+ }
2054
+ }
1904
2055
 
1905
2056
  class TooltipHelpers {
1906
2057
  static init() {
@@ -1990,6 +2141,322 @@ class AdminSidebarScroller {
1990
2141
  }
1991
2142
  }
1992
2143
 
2144
+ class CustomerGroupingRuleConfiguration {
2145
+ /**
2146
+ * @param {string} groupingRuleSelectSelector
2147
+ * @param {string} formGroupSelector
2148
+ */
2149
+ constructor(groupingRuleSelectSelector, formGroupSelector) {
2150
+ this.groupingRuleSelectSelector = groupingRuleSelectSelector;
2151
+ this.formGroupSelector = formGroupSelector;
2152
+ }
2153
+
2154
+ init() {
2155
+ // Event listener on dynamic elements
2156
+ $(document).on('change', this.groupingRuleSelectSelector, (selectEvent) => {
2157
+ this.toggleRuleConfigurationSection(selectEvent.target);
2158
+ });
2159
+
2160
+ // Show all sections with selected value
2161
+ document.querySelectorAll(this.groupingRuleSelectSelector).forEach((selectElement) => {
2162
+ this.toggleRuleConfigurationSection(selectElement);
2163
+ });
2164
+ }
2165
+
2166
+ toggleRuleConfigurationSection(selectElement) {
2167
+ const selectedRuleCode = selectElement.value;
2168
+ const configurationGroup = selectElement.parentElement.parentElement;
2169
+
2170
+ configurationGroup.querySelectorAll(this.formGroupSelector).forEach((formGroup) => {
2171
+ const groupRuleCode = formGroup.getAttribute('data-grouping-rule-code');
2172
+ const toggleFunction = groupRuleCode === selectedRuleCode ? this.showElement : this.hideElement;
2173
+
2174
+ toggleFunction(formGroup);
2175
+ });
2176
+ }
2177
+
2178
+ /** @private */
2179
+ hideElement(element) {
2180
+ element.classList.add('d-none');
2181
+ }
2182
+
2183
+ /** @private */
2184
+ showElement(element) {
2185
+ element.classList.remove('d-none');
2186
+ }
2187
+ }
2188
+
2189
+ class ProductVariantPricingSimulation {
2190
+ constructor(selector) {
2191
+ this.selector = selector;
2192
+ }
2193
+
2194
+ init() {
2195
+ document.querySelectorAll(this.selector).forEach((element) => {
2196
+ const productVariantId = element.getAttribute('data-product-variant-id');
2197
+ const channels = JSON.parse(element.getAttribute('data-channels'));
2198
+ const searchApiEndpoint = element.getAttribute('data-customer-search-url');
2199
+ const pricingApiEndpoint = element.getAttribute('data-pricing-url');
2200
+
2201
+ this.initElement(element, productVariantId, channels, searchApiEndpoint, pricingApiEndpoint);
2202
+ });
2203
+ }
2204
+
2205
+ initElement(parentElement, productVariantId, channels, searchApiEndpoint, pricingApiEndpoint) {
2206
+ parentElement.innerHTML = '';
2207
+
2208
+ const simulationData = {
2209
+ productVariantId: productVariantId,
2210
+ channelId: null,
2211
+ currencyId: null,
2212
+ customerId: null,
2213
+ };
2214
+
2215
+ const submitButton = document.createElement('button');
2216
+ const currencySelect = document.createElement('select');
2217
+
2218
+ const onChannelSelect = (event) => {
2219
+ const selectedChannelId = parseInt(event.target.value);
2220
+ simulationData.channelId = selectedChannelId;
2221
+ simulationData.currencyId = null;
2222
+ submitButton.disabled = this.isSubmitButtonDisabled(simulationData);
2223
+
2224
+ const selectedChannel = channels.find((channel) => {
2225
+ return channel.id === selectedChannelId
2226
+ });
2227
+
2228
+ currencySelect.innerHTML = '';
2229
+ this.addSelectOptions(currencySelect, selectedChannel.currencies, 'Select currency');
2230
+ };
2231
+
2232
+ const onCurrencySelect = (event) => {
2233
+ simulationData.currencyId = event.target.value;
2234
+ submitButton.disabled = this.isSubmitButtonDisabled(simulationData);
2235
+ };
2236
+
2237
+ const onCustomerSelect = (event) => {
2238
+ simulationData.customerId = event.target.value;
2239
+ submitButton.disabled = this.isSubmitButtonDisabled(simulationData);
2240
+ };
2241
+
2242
+ const graphWrapper = this.createGraphWrapper();
2243
+
2244
+ const onSubmit = () => {
2245
+ const pricingUrl = `${pricingApiEndpoint}?${this.serializeObjectToQuery(simulationData)}`;
2246
+
2247
+ axios$1
2248
+ .get(pricingUrl)
2249
+ .then((response) => {
2250
+ const chartData = response.data;
2251
+ graphWrapper.setAttribute('data-debug-chart', JSON.stringify(chartData)); // For e2e tests
2252
+ this.renderGraph(graphWrapper, chartData);
2253
+ })
2254
+ .catch((error) => {
2255
+ console.error(error);
2256
+ graphWrapper.innerHTML = 'Pricing simulation error';
2257
+ });
2258
+ };
2259
+
2260
+ const wrapper = document.createElement('div');
2261
+ wrapper.setAttribute('style', 'border: solid 1px #ddd; background-color: #fafafa;');
2262
+
2263
+ const filters = this.createFilters(
2264
+ channels,
2265
+ currencySelect,
2266
+ onChannelSelect,
2267
+ onCurrencySelect,
2268
+ onCustomerSelect,
2269
+ onSubmit,
2270
+ submitButton,
2271
+ searchApiEndpoint
2272
+ );
2273
+ filters.setAttribute(
2274
+ 'style',
2275
+ 'display: grid; grid-template-columns: 1fr 1fr 1fr 60px; grid-gap: 10px; ' +
2276
+ 'padding: 15px; border-bottom: solid 1px #ddd;'
2277
+ );
2278
+
2279
+ const graphFiller = this.createGraphFiller();
2280
+ graphWrapper.append(graphFiller);
2281
+
2282
+ wrapper.append(filters, graphWrapper);
2283
+ parentElement.append(wrapper);
2284
+ }
2285
+
2286
+ createFilters(
2287
+ channels,
2288
+ currencySelect,
2289
+ onChannelSelect,
2290
+ onCurrencySelect,
2291
+ onCustomerSelect,
2292
+ onSubmit,
2293
+ submitButton,
2294
+ searchApiEndpoint
2295
+ ) {
2296
+ const filtersWrapper = document.createElement('div');
2297
+
2298
+ const channelSelect = document.createElement('select');
2299
+ this.addSelectOptions(channelSelect, channels, 'Select channel');
2300
+ channelSelect.addEventListener('change', onChannelSelect);
2301
+
2302
+ currencySelect.addEventListener('change', onCurrencySelect);
2303
+
2304
+ const customerSelect = document.createElement('select');
2305
+ customerSelect.addEventListener('change', onCustomerSelect);
2306
+
2307
+ // this is delayed to avoid racing conditions
2308
+ setTimeout(() => this.hookClientSearchOnSelect(customerSelect, searchApiEndpoint), 600);
2309
+
2310
+ submitButton.disabled = true;
2311
+ submitButton.setAttribute('class', 'ui icon primary button');
2312
+ submitButton.type = 'button';
2313
+ submitButton.addEventListener('click', onSubmit);
2314
+
2315
+ const playIcon = document.createElement('i');
2316
+ playIcon.setAttribute('class', 'icon play');
2317
+ submitButton.append(playIcon);
2318
+
2319
+ filtersWrapper.append(channelSelect, currencySelect, customerSelect, submitButton);
2320
+
2321
+ return filtersWrapper
2322
+ }
2323
+
2324
+ createGraphWrapper() {
2325
+ const wrapper = document.createElement('div');
2326
+ wrapper.setAttribute('style', 'padding: 15px;');
2327
+
2328
+ return wrapper
2329
+ }
2330
+
2331
+ createGraphFiller() {
2332
+ const filler = document.createElement('div');
2333
+ filler.setAttribute(
2334
+ 'style',
2335
+ 'border-radius: 7px; background-color: #eee; height: 350px; display: flex; ' +
2336
+ 'justify-content: space-around; align-items: center; font-size: 4em;'
2337
+ );
2338
+
2339
+ const chartIcon = document.createElement('i');
2340
+ chartIcon.setAttribute('class', 'icon chart line');
2341
+ filler.append(chartIcon);
2342
+
2343
+ return filler
2344
+ }
2345
+
2346
+ addSelectOptions(select, choices, placeholder = null) {
2347
+ if (placeholder !== null) {
2348
+ const placeholderOption = document.createElement('option');
2349
+ placeholderOption.innerHTML = placeholder;
2350
+ placeholderOption.disabled = true;
2351
+ placeholderOption.selected = true;
2352
+ select.append(placeholderOption);
2353
+ }
2354
+
2355
+ for (const property in choices) {
2356
+ const choice = choices[property];
2357
+
2358
+ const channelOption = document.createElement('option');
2359
+ channelOption.innerHTML = choice.name;
2360
+ channelOption.setAttribute('value', choice.id);
2361
+ select.append(channelOption);
2362
+ }
2363
+
2364
+ return select
2365
+ }
2366
+
2367
+ hookClientSearchOnSelect(selectElement, searchApiEndpoint) {
2368
+ selectElement.setAttribute('class', `${selectElement.getAttribute('class')} search dropdown selection`);
2369
+
2370
+ $(selectElement).dropdown({
2371
+ apiSettings: {
2372
+ url: `${searchApiEndpoint}/{query}`,
2373
+ cache: false,
2374
+ },
2375
+
2376
+ minCharacters: 2,
2377
+ });
2378
+ }
2379
+
2380
+ isSubmitButtonDisabled(simulationData) {
2381
+ return (
2382
+ null === simulationData.productVariantId ||
2383
+ null === simulationData.channelId ||
2384
+ null === simulationData.currencyId ||
2385
+ null === simulationData.customerId
2386
+ )
2387
+ }
2388
+
2389
+ serializeObjectToQuery(object) {
2390
+ const query = [];
2391
+
2392
+ for (const part in object) {
2393
+ if (Object.prototype.hasOwnProperty.call(object, part)) {
2394
+ query.push(`${encodeURIComponent(part)}=${encodeURIComponent(object[part])}`);
2395
+ }
2396
+ }
2397
+
2398
+ return query.join('&')
2399
+ }
2400
+
2401
+ renderGraph(graphWrapper, graphData) {
2402
+ graphWrapper.innerHTML = '';
2403
+
2404
+ const graphCanvas = document.createElement('canvas');
2405
+ graphCanvas.width = 600;
2406
+ graphCanvas.height = 200;
2407
+
2408
+ graphWrapper.append(graphCanvas);
2409
+
2410
+ const graphConfig = this.getGraphConfig(graphData);
2411
+ console.log('graphConfig', graphConfig);
2412
+ new Chart$1(graphCanvas.getContext('2d'), graphConfig);
2413
+ }
2414
+
2415
+ /** @private */
2416
+ getGraphConfig(rawGraphData) {
2417
+ return {
2418
+ type: 'line',
2419
+ data: {
2420
+ datasets: rawGraphData.datasets.map((rawDataset) => {
2421
+ return {
2422
+ label: rawDataset.label,
2423
+ data: rawDataset.data,
2424
+ borderColor: '#0000ff',
2425
+ fill: false,
2426
+ steppedLine: true,
2427
+ pointRadius: 8,
2428
+ pointHoverRadius: 10,
2429
+ }
2430
+ }),
2431
+ },
2432
+ options: {
2433
+ maintainAspectRatio: true,
2434
+ responsive: true,
2435
+ scales: {
2436
+ xAxes: [
2437
+ {
2438
+ type: 'linear',
2439
+ position: 'bottom',
2440
+ scaleLabel: {
2441
+ display: true,
2442
+ labelString: rawGraphData.xAxisLabel,
2443
+ },
2444
+ },
2445
+ ],
2446
+ yAxes: [
2447
+ {
2448
+ scaleLabel: {
2449
+ display: true,
2450
+ labelString: rawGraphData.yAxisLabel,
2451
+ },
2452
+ },
2453
+ ],
2454
+ },
2455
+ },
2456
+ }
2457
+ }
2458
+ }
2459
+
1993
2460
  /**
1994
2461
  * CKEditor config
1995
2462
  */
@@ -2288,7 +2755,6 @@ vendorDescriptionElements.forEach((element) => {
2288
2755
  // Components initializations
2289
2756
  $(document).ready(() => {
2290
2757
  $('.ng-taxon-attr-dropdown').taxonAttributes();
2291
- $('.ng-feed-category-select').feedCategorySelect();
2292
2758
 
2293
2759
  $(document).previewUploadedImage('#sylius_payment_method_images');
2294
2760
  $(document).previewUploadedImage('#sylius_shipping_method_images');
@@ -2299,8 +2765,29 @@ $(document).ready(() => {
2299
2765
  */
2300
2766
  TooltipHelpers.init();
2301
2767
 
2768
+ const customerGroupingRuleConfiguration = new CustomerGroupingRuleConfiguration(
2769
+ 'select.ng-grouping-rule-select',
2770
+ '.ng-grouping-rule-configuration'
2771
+ );
2772
+ customerGroupingRuleConfiguration.init();
2773
+
2774
+ const feedCategorySelect = new FeedCategorySelect('.ng-feed-category-select');
2775
+ feedCategorySelect.init();
2776
+
2302
2777
  // Admin sidebar scroller
2303
2778
  const adminSidebarElement = document.getElementById('sidebar');
2304
2779
  const adminSidebarScroller = new AdminSidebarScroller(adminSidebarElement, 'a.item');
2305
2780
  adminSidebarScroller.scrollToActiveLink();
2781
+
2782
+ const productVariantPricingGraph = new ProductVariantPricingGraph('.ng-product-variant-pricing-graph');
2783
+ productVariantPricingGraph.init();
2784
+
2785
+ const productVariantPricingSimulation = new ProductVariantPricingSimulation(
2786
+ '.ng-product-variant-pricing-simulation'
2787
+ );
2788
+ productVariantPricingSimulation.init();
2789
+
2790
+ // Client search select
2791
+ const clientSearchSelect = new CustomerGroupClientAssigner('.ng-customer-group-client-assigner');
2792
+ clientSearchSelect.init();
2306
2793
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyshop/admin",
3
- "version": "1.3.35",
3
+ "version": "1.3.39",
4
4
  "author": "oXy Online s.r.o. <info@oxyshop.cz>",
5
5
  "main": "lib/index.js",
6
6
  "sass": "scss/main.scss",
package/scss/main.scss CHANGED
@@ -1,3 +1,7 @@
1
1
  @import 'Form/main';
2
2
  @import 'Logo/main';
3
3
  @import 'PickupPoint/main';
4
+
5
+ .d-none {
6
+ display: none !important;
7
+ }
@@ -0,0 +1,62 @@
1
+ import { axios } from '@oxyshop/shop/lib/plugins/Axios'
2
+
3
+ export default class CustomerGroupClientAssigner {
4
+ constructor(selector, searchSelector = '.ng-client-search-select', submitSelector = '.ng-client-assign-submit') {
5
+ this.selector = selector
6
+ this.searchSelector = searchSelector
7
+ this.submitSelector = submitSelector
8
+
9
+ this.containerElement = null
10
+ this.searchElement = null
11
+ this.submitElement = null
12
+ }
13
+
14
+ init() {
15
+ this.containerElement = document.querySelector(this.selector)
16
+ if (null === this.containerElement) {
17
+ return
18
+ }
19
+
20
+ const submitApiEndpoint = this.containerElement.getAttribute('data-assign-customers-url')
21
+ const redirectAfterUrl = this.containerElement.getAttribute('data-redirect-after-url')
22
+
23
+ this.searchElement = this.containerElement.querySelector(this.searchSelector)
24
+ const searchApiEndpoint = this.searchElement.getAttribute('data-customer-search-url')
25
+
26
+ $(this.searchElement).dropdown({
27
+ apiSettings: {
28
+ url: `${searchApiEndpoint}/{query}`,
29
+ cache: false,
30
+ },
31
+
32
+ minCharacters: 2,
33
+ })
34
+
35
+ this.submitElement = this.containerElement.querySelector(this.submitSelector)
36
+ this.submitElement.addEventListener('click', () => {
37
+ const selectedCustomersIds = this.getSelectValues(this.searchElement)
38
+ this.submitCustomers(submitApiEndpoint, selectedCustomersIds, redirectAfterUrl)
39
+ })
40
+ }
41
+
42
+ getSelectValues(selectElement) {
43
+ const selected = selectElement.querySelectorAll('option:checked')
44
+ return Array.from(selected).map((optionElement) => optionElement.value)
45
+ }
46
+
47
+ submitCustomers(apiEndpoint, selectedIds, redirectTo) {
48
+ this.submitElement.disabled = true
49
+
50
+ axios
51
+ .post(apiEndpoint, { customers: selectedIds })
52
+ .then(() => {
53
+ window.location = redirectTo
54
+ })
55
+ .catch((error) => {
56
+ console.error(error)
57
+ })
58
+ .finally(() => {
59
+ this.submitElement.disabled = false
60
+ })
61
+ }
62
+ }
@@ -0,0 +1,44 @@
1
+ export default class CustomerGroupingRuleConfiguration {
2
+ /**
3
+ * @param {string} groupingRuleSelectSelector
4
+ * @param {string} formGroupSelector
5
+ */
6
+ constructor(groupingRuleSelectSelector, formGroupSelector) {
7
+ this.groupingRuleSelectSelector = groupingRuleSelectSelector
8
+ this.formGroupSelector = formGroupSelector
9
+ }
10
+
11
+ init() {
12
+ // Event listener on dynamic elements
13
+ $(document).on('change', this.groupingRuleSelectSelector, (selectEvent) => {
14
+ this.toggleRuleConfigurationSection(selectEvent.target)
15
+ })
16
+
17
+ // Show all sections with selected value
18
+ document.querySelectorAll(this.groupingRuleSelectSelector).forEach((selectElement) => {
19
+ this.toggleRuleConfigurationSection(selectElement)
20
+ })
21
+ }
22
+
23
+ toggleRuleConfigurationSection(selectElement) {
24
+ const selectedRuleCode = selectElement.value
25
+ const configurationGroup = selectElement.parentElement.parentElement
26
+
27
+ configurationGroup.querySelectorAll(this.formGroupSelector).forEach((formGroup) => {
28
+ const groupRuleCode = formGroup.getAttribute('data-grouping-rule-code')
29
+ const toggleFunction = groupRuleCode === selectedRuleCode ? this.showElement : this.hideElement
30
+
31
+ toggleFunction(formGroup)
32
+ })
33
+ }
34
+
35
+ /** @private */
36
+ hideElement(element) {
37
+ element.classList.add('d-none')
38
+ }
39
+
40
+ /** @private */
41
+ showElement(element) {
42
+ element.classList.remove('d-none')
43
+ }
44
+ }
@@ -1,8 +1,10 @@
1
- $.fn.extend({
2
- feedCategorySelect() {
3
- const matchedElements = this
1
+ export default class FeedCategorySelect {
2
+ constructor(selector) {
3
+ this.selectSelector = selector
4
+ }
4
5
 
5
- Object.values(matchedElements).forEach((selectElement) => {
6
+ init() {
7
+ document.querySelectorAll(this.selectSelector).forEach((selectElement) => {
6
8
  const sourceCode = selectElement.getAttribute('data-source-code')
7
9
 
8
10
  $(selectElement).dropdown({
@@ -13,5 +15,5 @@ $.fn.extend({
13
15
  minCharacters: 2,
14
16
  })
15
17
  })
16
- },
17
- })
18
+ }
19
+ }
@@ -0,0 +1,86 @@
1
+ import Chart from 'chart.js'
2
+
3
+ export default class ProductVariantPricingGraph {
4
+ constructor(containerSelector) {
5
+ this.containerSelector = containerSelector
6
+ }
7
+
8
+ init() {
9
+ document.querySelectorAll(this.containerSelector).forEach((componentContainer) => {
10
+ const graphsData = JSON.parse(componentContainer.getAttribute('data-graph'))
11
+
12
+ for (const currencyGraphIndex in graphsData) {
13
+ const currencyGraph = graphsData[currencyGraphIndex]
14
+
15
+ const currencyCodeHeader = document.createElement('h4')
16
+ currencyCodeHeader.classList.add('ui', 'header')
17
+ currencyCodeHeader.textContent = currencyGraph.currency
18
+
19
+ const graphCanvas = document.createElement('canvas')
20
+ graphCanvas.width = 400
21
+ graphCanvas.height = 200
22
+
23
+ const graphContainer = document.createElement('div')
24
+ graphContainer.append(currencyCodeHeader)
25
+ graphContainer.append(graphCanvas)
26
+
27
+ componentContainer.append(graphContainer)
28
+
29
+ const graphConfig = this.getGraphConfig(currencyGraph.graph)
30
+ new Chart(graphCanvas.getContext('2d'), graphConfig)
31
+ }
32
+ })
33
+ }
34
+
35
+ /** @private */
36
+ getGraphConfig(rawGraphData) {
37
+ return {
38
+ type: 'line',
39
+ data: {
40
+ datasets: rawGraphData.datasets.map((rawDataset) => {
41
+ return {
42
+ label: rawDataset.label,
43
+ data: rawDataset.data,
44
+ borderColor: this.getRandomRgbColor(),
45
+ fill: false,
46
+ steppedLine: true,
47
+ pointRadius: 8,
48
+ pointHoverRadius: 10,
49
+ }
50
+ }),
51
+ },
52
+ options: {
53
+ maintainAspectRatio: true,
54
+ responsive: true,
55
+ scales: {
56
+ xAxes: [
57
+ {
58
+ type: 'linear',
59
+ position: 'bottom',
60
+ scaleLabel: {
61
+ display: true,
62
+ labelString: rawGraphData.xAxisLabel,
63
+ },
64
+ },
65
+ ],
66
+ yAxes: [
67
+ {
68
+ scaleLabel: {
69
+ display: true,
70
+ labelString: rawGraphData.yAxisLabel,
71
+ },
72
+ },
73
+ ],
74
+ },
75
+ },
76
+ }
77
+ }
78
+
79
+ /** @private */
80
+ getRandomRgbColor() {
81
+ const r = Math.floor(Math.random() * 255)
82
+ const g = Math.floor(Math.random() * 255)
83
+ const b = Math.floor(Math.random() * 255)
84
+ return `rgb(${r},${g},${b})`
85
+ }
86
+ }
@@ -0,0 +1,273 @@
1
+ import axios from 'axios'
2
+ import Chart from 'chart.js'
3
+
4
+ export default class ProductVariantPricingSimulation {
5
+ constructor(selector) {
6
+ this.selector = selector
7
+ }
8
+
9
+ init() {
10
+ document.querySelectorAll(this.selector).forEach((element) => {
11
+ const productVariantId = element.getAttribute('data-product-variant-id')
12
+ const channels = JSON.parse(element.getAttribute('data-channels'))
13
+ const searchApiEndpoint = element.getAttribute('data-customer-search-url')
14
+ const pricingApiEndpoint = element.getAttribute('data-pricing-url')
15
+
16
+ this.initElement(element, productVariantId, channels, searchApiEndpoint, pricingApiEndpoint)
17
+ })
18
+ }
19
+
20
+ initElement(parentElement, productVariantId, channels, searchApiEndpoint, pricingApiEndpoint) {
21
+ parentElement.innerHTML = ''
22
+
23
+ const simulationData = {
24
+ productVariantId: productVariantId,
25
+ channelId: null,
26
+ currencyId: null,
27
+ customerId: null,
28
+ }
29
+
30
+ const submitButton = document.createElement('button')
31
+ const currencySelect = document.createElement('select')
32
+
33
+ const onChannelSelect = (event) => {
34
+ const selectedChannelId = parseInt(event.target.value)
35
+ simulationData.channelId = selectedChannelId
36
+ simulationData.currencyId = null
37
+ submitButton.disabled = this.isSubmitButtonDisabled(simulationData)
38
+
39
+ const selectedChannel = channels.find((channel) => {
40
+ return channel.id === selectedChannelId
41
+ })
42
+
43
+ currencySelect.innerHTML = ''
44
+ this.addSelectOptions(currencySelect, selectedChannel.currencies, 'Select currency')
45
+ }
46
+
47
+ const onCurrencySelect = (event) => {
48
+ simulationData.currencyId = event.target.value
49
+ submitButton.disabled = this.isSubmitButtonDisabled(simulationData)
50
+ }
51
+
52
+ const onCustomerSelect = (event) => {
53
+ simulationData.customerId = event.target.value
54
+ submitButton.disabled = this.isSubmitButtonDisabled(simulationData)
55
+ }
56
+
57
+ const graphWrapper = this.createGraphWrapper()
58
+
59
+ const onSubmit = () => {
60
+ const pricingUrl = `${pricingApiEndpoint}?${this.serializeObjectToQuery(simulationData)}`
61
+
62
+ axios
63
+ .get(pricingUrl)
64
+ .then((response) => {
65
+ const chartData = response.data
66
+ graphWrapper.setAttribute('data-debug-chart', JSON.stringify(chartData)) // For e2e tests
67
+ this.renderGraph(graphWrapper, chartData)
68
+ })
69
+ .catch((error) => {
70
+ console.error(error)
71
+ graphWrapper.innerHTML = 'Pricing simulation error'
72
+ })
73
+ }
74
+
75
+ const wrapper = document.createElement('div')
76
+ wrapper.setAttribute('style', 'border: solid 1px #ddd; background-color: #fafafa;')
77
+
78
+ const filters = this.createFilters(
79
+ channels,
80
+ currencySelect,
81
+ onChannelSelect,
82
+ onCurrencySelect,
83
+ onCustomerSelect,
84
+ onSubmit,
85
+ submitButton,
86
+ searchApiEndpoint
87
+ )
88
+ filters.setAttribute(
89
+ 'style',
90
+ 'display: grid; grid-template-columns: 1fr 1fr 1fr 60px; grid-gap: 10px; ' +
91
+ 'padding: 15px; border-bottom: solid 1px #ddd;'
92
+ )
93
+
94
+ const graphFiller = this.createGraphFiller()
95
+ graphWrapper.append(graphFiller)
96
+
97
+ wrapper.append(filters, graphWrapper)
98
+ parentElement.append(wrapper)
99
+ }
100
+
101
+ createFilters(
102
+ channels,
103
+ currencySelect,
104
+ onChannelSelect,
105
+ onCurrencySelect,
106
+ onCustomerSelect,
107
+ onSubmit,
108
+ submitButton,
109
+ searchApiEndpoint
110
+ ) {
111
+ const filtersWrapper = document.createElement('div')
112
+
113
+ const channelSelect = document.createElement('select')
114
+ this.addSelectOptions(channelSelect, channels, 'Select channel')
115
+ channelSelect.addEventListener('change', onChannelSelect)
116
+
117
+ currencySelect.addEventListener('change', onCurrencySelect)
118
+
119
+ const customerSelect = document.createElement('select')
120
+ customerSelect.addEventListener('change', onCustomerSelect)
121
+
122
+ // this is delayed to avoid racing conditions
123
+ setTimeout(() => this.hookClientSearchOnSelect(customerSelect, searchApiEndpoint), 600)
124
+
125
+ submitButton.disabled = true
126
+ submitButton.setAttribute('class', 'ui icon primary button')
127
+ submitButton.type = 'button'
128
+ submitButton.addEventListener('click', onSubmit)
129
+
130
+ const playIcon = document.createElement('i')
131
+ playIcon.setAttribute('class', 'icon play')
132
+ submitButton.append(playIcon)
133
+
134
+ filtersWrapper.append(channelSelect, currencySelect, customerSelect, submitButton)
135
+
136
+ return filtersWrapper
137
+ }
138
+
139
+ createGraphWrapper() {
140
+ const wrapper = document.createElement('div')
141
+ wrapper.setAttribute('style', 'padding: 15px;')
142
+
143
+ return wrapper
144
+ }
145
+
146
+ createGraphFiller() {
147
+ const filler = document.createElement('div')
148
+ filler.setAttribute(
149
+ 'style',
150
+ 'border-radius: 7px; background-color: #eee; height: 350px; display: flex; ' +
151
+ 'justify-content: space-around; align-items: center; font-size: 4em;'
152
+ )
153
+
154
+ const chartIcon = document.createElement('i')
155
+ chartIcon.setAttribute('class', 'icon chart line')
156
+ filler.append(chartIcon)
157
+
158
+ return filler
159
+ }
160
+
161
+ addSelectOptions(select, choices, placeholder = null) {
162
+ if (placeholder !== null) {
163
+ const placeholderOption = document.createElement('option')
164
+ placeholderOption.innerHTML = placeholder
165
+ placeholderOption.disabled = true
166
+ placeholderOption.selected = true
167
+ select.append(placeholderOption)
168
+ }
169
+
170
+ for (const property in choices) {
171
+ const choice = choices[property]
172
+
173
+ const channelOption = document.createElement('option')
174
+ channelOption.innerHTML = choice.name
175
+ channelOption.setAttribute('value', choice.id)
176
+ select.append(channelOption)
177
+ }
178
+
179
+ return select
180
+ }
181
+
182
+ hookClientSearchOnSelect(selectElement, searchApiEndpoint) {
183
+ selectElement.setAttribute('class', `${selectElement.getAttribute('class')} search dropdown selection`)
184
+
185
+ $(selectElement).dropdown({
186
+ apiSettings: {
187
+ url: `${searchApiEndpoint}/{query}`,
188
+ cache: false,
189
+ },
190
+
191
+ minCharacters: 2,
192
+ })
193
+ }
194
+
195
+ isSubmitButtonDisabled(simulationData) {
196
+ return (
197
+ null === simulationData.productVariantId ||
198
+ null === simulationData.channelId ||
199
+ null === simulationData.currencyId ||
200
+ null === simulationData.customerId
201
+ )
202
+ }
203
+
204
+ serializeObjectToQuery(object) {
205
+ const query = []
206
+
207
+ for (const part in object) {
208
+ if (Object.prototype.hasOwnProperty.call(object, part)) {
209
+ query.push(`${encodeURIComponent(part)}=${encodeURIComponent(object[part])}`)
210
+ }
211
+ }
212
+
213
+ return query.join('&')
214
+ }
215
+
216
+ renderGraph(graphWrapper, graphData) {
217
+ graphWrapper.innerHTML = ''
218
+
219
+ const graphCanvas = document.createElement('canvas')
220
+ graphCanvas.width = 600
221
+ graphCanvas.height = 200
222
+
223
+ graphWrapper.append(graphCanvas)
224
+
225
+ const graphConfig = this.getGraphConfig(graphData)
226
+ console.log('graphConfig', graphConfig)
227
+ new Chart(graphCanvas.getContext('2d'), graphConfig)
228
+ }
229
+
230
+ /** @private */
231
+ getGraphConfig(rawGraphData) {
232
+ return {
233
+ type: 'line',
234
+ data: {
235
+ datasets: rawGraphData.datasets.map((rawDataset) => {
236
+ return {
237
+ label: rawDataset.label,
238
+ data: rawDataset.data,
239
+ borderColor: '#0000ff',
240
+ fill: false,
241
+ steppedLine: true,
242
+ pointRadius: 8,
243
+ pointHoverRadius: 10,
244
+ }
245
+ }),
246
+ },
247
+ options: {
248
+ maintainAspectRatio: true,
249
+ responsive: true,
250
+ scales: {
251
+ xAxes: [
252
+ {
253
+ type: 'linear',
254
+ position: 'bottom',
255
+ scaleLabel: {
256
+ display: true,
257
+ labelString: rawGraphData.xAxisLabel,
258
+ },
259
+ },
260
+ ],
261
+ yAxes: [
262
+ {
263
+ scaleLabel: {
264
+ display: true,
265
+ labelString: rawGraphData.yAxisLabel,
266
+ },
267
+ },
268
+ ],
269
+ },
270
+ },
271
+ }
272
+ }
273
+ }
package/src/index.js CHANGED
@@ -6,9 +6,13 @@ import 'sylius-bundle/AdminBundle/Resources/private/js/app'
6
6
 
7
7
  // Scripts - components
8
8
  import './components/taxonAttributes'
9
- import './components/feedCategorySelect'
9
+ import FeedCategorySelect from './components/feedCategorySelect'
10
+ import ProductVariantPricingGraph from './components/productVariantPricingGraph'
11
+ import CustomerGroupClientAssigner from './components/customerGroupClientAssigner'
10
12
  import TooltipHelpers from './components/tooltipHelpers'
11
13
  import AdminSidebarScroller from './components/adminSidebarScroller'
14
+ import CustomerGroupingRuleConfiguration from './components/customerGroupingRuleConfiguration'
15
+ import ProductVariantPricingSimulation from './components/productVariantPricingSimulation'
12
16
 
13
17
  // Scripts - plugin
14
18
  import './plugins/ckeditor/index'
@@ -32,7 +36,6 @@ import '@oxyshop/admin/images/admin-logo.svg'
32
36
  // Components initializations
33
37
  $(document).ready(() => {
34
38
  $('.ng-taxon-attr-dropdown').taxonAttributes()
35
- $('.ng-feed-category-select').feedCategorySelect()
36
39
 
37
40
  $(document).previewUploadedImage('#sylius_payment_method_images')
38
41
  $(document).previewUploadedImage('#sylius_shipping_method_images')
@@ -43,8 +46,29 @@ $(document).ready(() => {
43
46
  */
44
47
  TooltipHelpers.init()
45
48
 
49
+ const customerGroupingRuleConfiguration = new CustomerGroupingRuleConfiguration(
50
+ 'select.ng-grouping-rule-select',
51
+ '.ng-grouping-rule-configuration'
52
+ )
53
+ customerGroupingRuleConfiguration.init()
54
+
55
+ const feedCategorySelect = new FeedCategorySelect('.ng-feed-category-select')
56
+ feedCategorySelect.init()
57
+
46
58
  // Admin sidebar scroller
47
59
  const adminSidebarElement = document.getElementById('sidebar')
48
60
  const adminSidebarScroller = new AdminSidebarScroller(adminSidebarElement, 'a.item')
49
61
  adminSidebarScroller.scrollToActiveLink()
62
+
63
+ const productVariantPricingGraph = new ProductVariantPricingGraph('.ng-product-variant-pricing-graph')
64
+ productVariantPricingGraph.init()
65
+
66
+ const productVariantPricingSimulation = new ProductVariantPricingSimulation(
67
+ '.ng-product-variant-pricing-simulation'
68
+ )
69
+ productVariantPricingSimulation.init()
70
+
71
+ // Client search select
72
+ const clientSearchSelect = new CustomerGroupClientAssigner('.ng-customer-group-client-assigner')
73
+ clientSearchSelect.init()
50
74
  })