@oxyshop/admin 1.3.49 → 1.3.51
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/lib/index.js +937 -79
- package/lib/style.css +714 -361
- package/package.json +6 -6
- package/src/components/article-slug.js +65 -0
- package/src/components/blog-category-slug.js +71 -0
- package/src/components/blog-slug.js +65 -0
- package/src/components/documentUploadInput.js +33 -0
- package/src/components/feedCategorySelect.js +6 -0
- package/src/components/imageInput.js +41 -0
- package/src/components/productVariantPricingGraph.js +86 -0
- package/src/components/productVariantPricingSimulation.js +276 -0
- package/src/index.js +22 -1
- package/src/plugins/ckeditor/index.js +29 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxyshop/admin",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.51",
|
|
4
4
|
"author": "oXy Online s.r.o. <info@oxyshop.cz>",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"sass": "scss/main.scss",
|
|
@@ -19,15 +19,15 @@
|
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@oxyshop/core": "^1.3.0",
|
|
22
|
-
"chart.js": "~
|
|
22
|
+
"chart.js": "~3.7.0",
|
|
23
23
|
"jquery.dirtyforms": "~2.0.0",
|
|
24
24
|
"lightbox2": "~2.11.0",
|
|
25
|
-
"semantic-ui-css": "~2.
|
|
25
|
+
"semantic-ui-css": "~2.5.0",
|
|
26
26
|
"slick-carousel": "~1.8.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"@rollup/plugin-alias": "~
|
|
30
|
-
"rollup": "~2.
|
|
31
|
-
"rollup-plugin-scss": "~
|
|
29
|
+
"@rollup/plugin-alias": "~4.0.0",
|
|
30
|
+
"rollup": "~2.79.0",
|
|
31
|
+
"rollup-plugin-scss": "~4.0.0"
|
|
32
32
|
}
|
|
33
33
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
;(function ($) {
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
$.fn.extend({
|
|
5
|
+
articleSlugGenerator: function () {
|
|
6
|
+
let timeout
|
|
7
|
+
|
|
8
|
+
$('[name*="nextgen_cms_bundle_article[translations]"][name*="[name]"]').on('input', function () {
|
|
9
|
+
clearTimeout(timeout)
|
|
10
|
+
let element = $(this)
|
|
11
|
+
|
|
12
|
+
timeout = setTimeout(function () {
|
|
13
|
+
updateSlug(element)
|
|
14
|
+
}, 1000)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
$('.toggle-article-slug-modification').on('click', function (e) {
|
|
18
|
+
e.preventDefault()
|
|
19
|
+
toggleSlugModification($(this), $(this).siblings('input'))
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
function updateSlug(element) {
|
|
23
|
+
let slugInput = element.parents('.content').find('[name*="[slug]"]')
|
|
24
|
+
let loadableParent = slugInput.parents('.field.loadable')
|
|
25
|
+
|
|
26
|
+
if ('readonly' === slugInput.attr('readonly')) {
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
loadableParent.addClass('loading')
|
|
31
|
+
|
|
32
|
+
$.ajax({
|
|
33
|
+
type: 'GET',
|
|
34
|
+
url: slugInput.attr('data-url'),
|
|
35
|
+
data: { name: element.val() },
|
|
36
|
+
dataType: 'json',
|
|
37
|
+
accept: 'application/json',
|
|
38
|
+
success: function (data) {
|
|
39
|
+
slugInput.val(data.slug)
|
|
40
|
+
if (slugInput.parents('.field').hasClass('error')) {
|
|
41
|
+
slugInput.parents('.field').removeClass('error')
|
|
42
|
+
slugInput.parents('.field').find('.sylius-validation-error').remove()
|
|
43
|
+
}
|
|
44
|
+
loadableParent.removeClass('loading')
|
|
45
|
+
},
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function toggleSlugModification(button, slugInput) {
|
|
50
|
+
if (slugInput.attr('readonly')) {
|
|
51
|
+
slugInput.removeAttr('readonly')
|
|
52
|
+
button.html('<i class="unlock icon"></i>')
|
|
53
|
+
} else {
|
|
54
|
+
slugInput.attr('readonly', 'readonly')
|
|
55
|
+
button.html('<i class="lock icon"></i>')
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
})
|
|
60
|
+
})(jQuery)
|
|
61
|
+
;(function ($) {
|
|
62
|
+
$(document).ready(function () {
|
|
63
|
+
$(this).articleSlugGenerator()
|
|
64
|
+
})
|
|
65
|
+
})(jQuery)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
;(function ($) {
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
$.fn.extend({
|
|
5
|
+
blogCategorySlugGenerator: function () {
|
|
6
|
+
let timeout
|
|
7
|
+
|
|
8
|
+
$('[name*="nextgen_cms_bundle_blog_category[translations]"][name*="[name]"]').on('input', function () {
|
|
9
|
+
clearTimeout(timeout)
|
|
10
|
+
let element = $(this)
|
|
11
|
+
|
|
12
|
+
timeout = setTimeout(function () {
|
|
13
|
+
updateSlug(element)
|
|
14
|
+
}, 1000)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
$('.toggle-blog-category-slug-modification').on('click', function (e) {
|
|
18
|
+
e.preventDefault()
|
|
19
|
+
toggleSlugModification($(this), $(this).siblings('input'))
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
function updateSlug(element) {
|
|
23
|
+
let slugInput = element.parents('.content').find('[name*="[slug]"]')
|
|
24
|
+
let loadableParent = slugInput.parents('.field.loadable')
|
|
25
|
+
|
|
26
|
+
if ('readonly' === slugInput.attr('readonly')) {
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
loadableParent.addClass('loading')
|
|
31
|
+
|
|
32
|
+
let data
|
|
33
|
+
data = {
|
|
34
|
+
name: element.val(),
|
|
35
|
+
locale: element.closest('[data-locale]').data('locale'),
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
$.ajax({
|
|
39
|
+
type: 'GET',
|
|
40
|
+
url: slugInput.attr('data-url'),
|
|
41
|
+
data: data,
|
|
42
|
+
dataType: 'json',
|
|
43
|
+
accept: 'application/json',
|
|
44
|
+
success: function (data) {
|
|
45
|
+
slugInput.val(data.slug)
|
|
46
|
+
if (slugInput.parents('.field').hasClass('error')) {
|
|
47
|
+
slugInput.parents('.field').removeClass('error')
|
|
48
|
+
slugInput.parents('.field').find('.sylius-validation-error').remove()
|
|
49
|
+
}
|
|
50
|
+
loadableParent.removeClass('loading')
|
|
51
|
+
},
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function toggleSlugModification(button, slugInput) {
|
|
56
|
+
if (slugInput.attr('readonly')) {
|
|
57
|
+
slugInput.removeAttr('readonly')
|
|
58
|
+
button.html('<i class="unlock icon"></i>')
|
|
59
|
+
} else {
|
|
60
|
+
slugInput.attr('readonly', 'readonly')
|
|
61
|
+
button.html('<i class="lock icon"></i>')
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
})
|
|
66
|
+
})(jQuery)
|
|
67
|
+
;(function ($) {
|
|
68
|
+
$(document).ready(function () {
|
|
69
|
+
$(this).blogCategorySlugGenerator()
|
|
70
|
+
})
|
|
71
|
+
})(jQuery)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
;(function ($) {
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
$.fn.extend({
|
|
5
|
+
blogSlugGenerator: function () {
|
|
6
|
+
let timeout
|
|
7
|
+
|
|
8
|
+
$('[name*="nextgen_cms_bundle_blog[translations]"][name*="[name]"]').on('input', function () {
|
|
9
|
+
clearTimeout(timeout)
|
|
10
|
+
let element = $(this)
|
|
11
|
+
|
|
12
|
+
timeout = setTimeout(function () {
|
|
13
|
+
updateSlug(element)
|
|
14
|
+
}, 1000)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
$('.toggle-blog-slug-modification').on('click', function (e) {
|
|
18
|
+
e.preventDefault()
|
|
19
|
+
toggleSlugModification($(this), $(this).siblings('input'))
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
function updateSlug(element) {
|
|
23
|
+
let slugInput = element.parents('.content').find('[name*="[slug]"]')
|
|
24
|
+
let loadableParent = slugInput.parents('.field.loadable')
|
|
25
|
+
|
|
26
|
+
if ('readonly' === slugInput.attr('readonly')) {
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
loadableParent.addClass('loading')
|
|
31
|
+
|
|
32
|
+
$.ajax({
|
|
33
|
+
type: 'GET',
|
|
34
|
+
url: slugInput.attr('data-url'),
|
|
35
|
+
data: { name: element.val() },
|
|
36
|
+
dataType: 'json',
|
|
37
|
+
accept: 'application/json',
|
|
38
|
+
success: function (data) {
|
|
39
|
+
slugInput.val(data.slug)
|
|
40
|
+
if (slugInput.parents('.field').hasClass('error')) {
|
|
41
|
+
slugInput.parents('.field').removeClass('error')
|
|
42
|
+
slugInput.parents('.field').find('.sylius-validation-error').remove()
|
|
43
|
+
}
|
|
44
|
+
loadableParent.removeClass('loading')
|
|
45
|
+
},
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function toggleSlugModification(button, slugInput) {
|
|
50
|
+
if (slugInput.attr('readonly')) {
|
|
51
|
+
slugInput.removeAttr('readonly')
|
|
52
|
+
button.html('<i class="unlock icon"></i>')
|
|
53
|
+
} else {
|
|
54
|
+
slugInput.attr('readonly', 'readonly')
|
|
55
|
+
button.html('<i class="lock icon"></i>')
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
})
|
|
60
|
+
})(jQuery)
|
|
61
|
+
;(function ($) {
|
|
62
|
+
$(document).ready(function () {
|
|
63
|
+
$(this).blogSlugGenerator()
|
|
64
|
+
})
|
|
65
|
+
})(jQuery)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export default class DocumentUploadInput {
|
|
2
|
+
/** @param {string} documentUploadInputSelector */
|
|
3
|
+
constructor(documentUploadInputSelector) {
|
|
4
|
+
this.selector = documentUploadInputSelector
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
init() {
|
|
8
|
+
const fileInputs = document.querySelectorAll(this.selector)
|
|
9
|
+
|
|
10
|
+
Array.prototype.forEach.call(fileInputs, (inputElement) => {
|
|
11
|
+
const fileUrl = inputElement.getAttribute('data-file-url')
|
|
12
|
+
const fileTitle = inputElement.getAttribute('data-file-name')
|
|
13
|
+
const downloadText = inputElement.getAttribute('data-download-translation')
|
|
14
|
+
|
|
15
|
+
const iconElement = document.createElement('i')
|
|
16
|
+
iconElement.classList.add('download', 'icon')
|
|
17
|
+
|
|
18
|
+
const buttonElement = document.createElement('button')
|
|
19
|
+
buttonElement.classList.add('ui', 'compact', 'labeled', 'icon', 'button')
|
|
20
|
+
buttonElement.setAttribute('type', 'button')
|
|
21
|
+
buttonElement.append(iconElement)
|
|
22
|
+
buttonElement.innerHTML += `${downloadText}: ${fileTitle}`
|
|
23
|
+
|
|
24
|
+
const downloadLink = document.createElement('a')
|
|
25
|
+
downloadLink.setAttribute('style', 'display: inline-block; margin: -5px 0 25px 0;')
|
|
26
|
+
downloadLink.setAttribute('target', '_blank')
|
|
27
|
+
downloadLink.setAttribute('href', fileUrl)
|
|
28
|
+
downloadLink.append(buttonElement)
|
|
29
|
+
|
|
30
|
+
inputElement.parentNode.parentNode.append(downloadLink)
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -13,7 +13,13 @@ export default class FeedCategorySelect {
|
|
|
13
13
|
cache: false,
|
|
14
14
|
},
|
|
15
15
|
minCharacters: 2,
|
|
16
|
+
clearable: true,
|
|
16
17
|
})
|
|
18
|
+
|
|
19
|
+
// Workaround for dropdown clear button not showing after loading initial value from the API
|
|
20
|
+
setTimeout(() => {
|
|
21
|
+
$(selectElement).dropdown('restore defaults')
|
|
22
|
+
}, 0)
|
|
17
23
|
})
|
|
18
24
|
}
|
|
19
25
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export default class ImageInput {
|
|
2
|
+
constructor(selector) {
|
|
3
|
+
this.selector = selector
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
init() {
|
|
7
|
+
document.querySelectorAll(this.selector).forEach((element) => {
|
|
8
|
+
element.querySelectorAll('input[type="file"]').forEach((input) => {
|
|
9
|
+
input.addEventListener('change', () => {
|
|
10
|
+
this.onImageInputChange(input)
|
|
11
|
+
})
|
|
12
|
+
})
|
|
13
|
+
})
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
onImageInputChange(input) {
|
|
17
|
+
if (!input.files || !input.files[0]) {
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const reader = new FileReader()
|
|
22
|
+
|
|
23
|
+
reader.onload = (event) => {
|
|
24
|
+
const wrapperDiv = input.parentElement.parentElement
|
|
25
|
+
const image = wrapperDiv.querySelector('.image')
|
|
26
|
+
|
|
27
|
+
if (null === image) {
|
|
28
|
+
const img = document.createElement('img')
|
|
29
|
+
img.setAttribute('class', 'ui small bordered image')
|
|
30
|
+
img.setAttribute('src', event.target.result)
|
|
31
|
+
|
|
32
|
+
const inputLabel = wrapperDiv.querySelector(':scope > label')
|
|
33
|
+
wrapperDiv.insertBefore(img, inputLabel.nextSibling)
|
|
34
|
+
} else {
|
|
35
|
+
image.setAttribute('src', event.target.result)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
reader.readAsDataURL(input.files[0])
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -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,276 @@
|
|
|
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
|
+
channelSelect.classList.add('dirtyignore')
|
|
115
|
+
this.addSelectOptions(channelSelect, channels, 'Select channel')
|
|
116
|
+
channelSelect.addEventListener('change', onChannelSelect)
|
|
117
|
+
|
|
118
|
+
currencySelect.classList.add('dirtyignore')
|
|
119
|
+
currencySelect.addEventListener('change', onCurrencySelect)
|
|
120
|
+
|
|
121
|
+
const customerSelect = document.createElement('select')
|
|
122
|
+
customerSelect.classList.add('dirtyignore')
|
|
123
|
+
customerSelect.addEventListener('change', onCustomerSelect)
|
|
124
|
+
|
|
125
|
+
// this is delayed to avoid racing conditions
|
|
126
|
+
setTimeout(() => this.hookClientSearchOnSelect(customerSelect, searchApiEndpoint), 600)
|
|
127
|
+
|
|
128
|
+
submitButton.disabled = true
|
|
129
|
+
submitButton.setAttribute('class', 'ui icon primary button')
|
|
130
|
+
submitButton.type = 'button'
|
|
131
|
+
submitButton.addEventListener('click', onSubmit)
|
|
132
|
+
|
|
133
|
+
const playIcon = document.createElement('i')
|
|
134
|
+
playIcon.setAttribute('class', 'icon play')
|
|
135
|
+
submitButton.append(playIcon)
|
|
136
|
+
|
|
137
|
+
filtersWrapper.append(channelSelect, currencySelect, customerSelect, submitButton)
|
|
138
|
+
|
|
139
|
+
return filtersWrapper
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
createGraphWrapper() {
|
|
143
|
+
const wrapper = document.createElement('div')
|
|
144
|
+
wrapper.setAttribute('style', 'padding: 15px;')
|
|
145
|
+
|
|
146
|
+
return wrapper
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
createGraphFiller() {
|
|
150
|
+
const filler = document.createElement('div')
|
|
151
|
+
filler.setAttribute(
|
|
152
|
+
'style',
|
|
153
|
+
'border-radius: 7px; background-color: #eee; height: 350px; display: flex; ' +
|
|
154
|
+
'justify-content: space-around; align-items: center; font-size: 4em;'
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
const chartIcon = document.createElement('i')
|
|
158
|
+
chartIcon.setAttribute('class', 'icon chart line')
|
|
159
|
+
filler.append(chartIcon)
|
|
160
|
+
|
|
161
|
+
return filler
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
addSelectOptions(select, choices, placeholder = null) {
|
|
165
|
+
if (placeholder !== null) {
|
|
166
|
+
const placeholderOption = document.createElement('option')
|
|
167
|
+
placeholderOption.innerHTML = placeholder
|
|
168
|
+
placeholderOption.disabled = true
|
|
169
|
+
placeholderOption.selected = true
|
|
170
|
+
select.append(placeholderOption)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
for (const property in choices) {
|
|
174
|
+
const choice = choices[property]
|
|
175
|
+
|
|
176
|
+
const channelOption = document.createElement('option')
|
|
177
|
+
channelOption.innerHTML = choice.name
|
|
178
|
+
channelOption.setAttribute('value', choice.id)
|
|
179
|
+
select.append(channelOption)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return select
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
hookClientSearchOnSelect(selectElement, searchApiEndpoint) {
|
|
186
|
+
selectElement.setAttribute('class', `${selectElement.getAttribute('class')} search dropdown selection`)
|
|
187
|
+
|
|
188
|
+
$(selectElement).dropdown({
|
|
189
|
+
apiSettings: {
|
|
190
|
+
url: `${searchApiEndpoint}/{query}`,
|
|
191
|
+
cache: false,
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
minCharacters: 2,
|
|
195
|
+
})
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
isSubmitButtonDisabled(simulationData) {
|
|
199
|
+
return (
|
|
200
|
+
null === simulationData.productVariantId ||
|
|
201
|
+
null === simulationData.channelId ||
|
|
202
|
+
null === simulationData.currencyId ||
|
|
203
|
+
null === simulationData.customerId
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
serializeObjectToQuery(object) {
|
|
208
|
+
const query = []
|
|
209
|
+
|
|
210
|
+
for (const part in object) {
|
|
211
|
+
if (Object.prototype.hasOwnProperty.call(object, part)) {
|
|
212
|
+
query.push(`${encodeURIComponent(part)}=${encodeURIComponent(object[part])}`)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return query.join('&')
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
renderGraph(graphWrapper, graphData) {
|
|
220
|
+
graphWrapper.innerHTML = ''
|
|
221
|
+
|
|
222
|
+
const graphCanvas = document.createElement('canvas')
|
|
223
|
+
graphCanvas.width = 600
|
|
224
|
+
graphCanvas.height = 200
|
|
225
|
+
|
|
226
|
+
graphWrapper.append(graphCanvas)
|
|
227
|
+
|
|
228
|
+
const graphConfig = this.getGraphConfig(graphData)
|
|
229
|
+
console.log('graphConfig', graphConfig)
|
|
230
|
+
new Chart(graphCanvas.getContext('2d'), graphConfig)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** @private */
|
|
234
|
+
getGraphConfig(rawGraphData) {
|
|
235
|
+
return {
|
|
236
|
+
type: 'line',
|
|
237
|
+
data: {
|
|
238
|
+
datasets: rawGraphData.datasets.map((rawDataset) => {
|
|
239
|
+
return {
|
|
240
|
+
label: rawDataset.label,
|
|
241
|
+
data: rawDataset.data,
|
|
242
|
+
borderColor: '#0000ff',
|
|
243
|
+
fill: false,
|
|
244
|
+
steppedLine: true,
|
|
245
|
+
pointRadius: 8,
|
|
246
|
+
pointHoverRadius: 10,
|
|
247
|
+
}
|
|
248
|
+
}),
|
|
249
|
+
},
|
|
250
|
+
options: {
|
|
251
|
+
maintainAspectRatio: true,
|
|
252
|
+
responsive: true,
|
|
253
|
+
scales: {
|
|
254
|
+
xAxes: [
|
|
255
|
+
{
|
|
256
|
+
type: 'linear',
|
|
257
|
+
position: 'bottom',
|
|
258
|
+
scaleLabel: {
|
|
259
|
+
display: true,
|
|
260
|
+
labelString: rawGraphData.xAxisLabel,
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
yAxes: [
|
|
265
|
+
{
|
|
266
|
+
scaleLabel: {
|
|
267
|
+
display: true,
|
|
268
|
+
labelString: rawGraphData.yAxisLabel,
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
],
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|