Skip to content

Commit a5fd64c

Browse files
Improve administration order detail editing (#4637)
2 parents 604312f + 0442e4c commit a5fd64c

52 files changed

Lines changed: 1143 additions & 1264 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

assets/js/admin/components/ExecuteRefund.js

Lines changed: 0 additions & 15 deletions
This file was deleted.

assets/js/admin/components/FormChangeInfo.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export default class FormChangeInfo {
1111
initContent($container) {
1212
$container
1313
.filterAllNodes('.page-body form:has(button[type="submit"])')
14+
.not('[data-no-form-change-info]')
1415
.change(() => FormChangeInfo.showInfo())
1516
.each(function () {
1617
if ($(this).find('.form-input-error:first, .js-validation-errors-list li:first').length > 0) {

assets/js/admin/components/OrderItems.js

Lines changed: 189 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,167 +1,248 @@
11
import ConfirmWindow from '@shopsys/administration/src/js/utils/confirmWindow';
2-
import ModalWindow from '@shopsys/administration/src/js/utils/modalWindow';
2+
import { getComponent } from '@symfony/ux-live-component';
33
import { Tooltip } from '@tabler/core';
44
import Translator from 'bazinga-translator';
5-
import Ajax from '../../common/utils/Ajax';
65
import { escapeHtml } from '../../common/utils/escapeHtml';
76
import Register from '../../common/utils/Register';
87
import FormChangeInfo from './FormChangeInfo';
98
import ProductPicker from './ProductPicker';
109

1110
export default class OrderItems {
12-
static textDisabledClass = 'text-secondary';
11+
static savedEventBound = false;
12+
static liveComponentsWithRenderHook = new WeakSet();
1313

1414
constructor($container) {
15-
const $collection = $container.filterAllNodes('#js-order-items');
16-
$collection.on('click', '.js-order-item-remove', event => this.onRemoveItemClick(event));
17-
$container.filterAllNodes('#js-order-item-add').on('click', event => this.onAddItemClick(event));
15+
this.$container = $container;
16+
this.$card = $container.filterAllNodes('.js-order-items-card');
17+
this.$form = this.$card.closest('form');
18+
this.$liveComponent = this.$card.closest('[data-controller~="live"]');
19+
this.liveComponentPromise = null;
20+
this.pendingAction = false;
21+
22+
if (this.$liveComponent.length === 0) {
23+
return;
24+
}
1825

19-
this.tooltip = null;
20-
this.refreshCount($collection);
21-
// eslint-disable-next-line no-new
22-
new ProductPicker($container.filterAllNodes('#js-order-item-add-product'), (productId, productName) => {
23-
this.addProduct(productId, productName);
24-
});
26+
this.bindEventHandlers();
27+
this.initializeProductPickers();
28+
this.registerLiveRenderHook();
29+
OrderItems.initializeDynamicElements(this.$card);
2530
}
2631

27-
refreshCount($collection) {
28-
const $items = $collection.find('.js-order-item');
32+
bindEventHandlers() {
33+
this.$form.off('change.orderItems').on('change.orderItems', event => this.onFormChange(event));
34+
this.$card
35+
.off('click.orderItems', '.js-order-items-add-item')
36+
.on('click.orderItems', '.js-order-items-add-item', event => this.addItem(event));
37+
this.$card
38+
.off('click.orderItems', '.js-order-item-remove')
39+
.on('click.orderItems', '.js-order-item-remove', event => this.onRemoveItemClick(event));
40+
this.$card
41+
.off('change.orderItems', '.js-set-prices-manually')
42+
.on('change.orderItems', '.js-set-prices-manually', event => {
43+
OrderItems.onPriceCalculationChange($(event.currentTarget).closest('.js-order-item-any'));
44+
});
45+
this.$card
46+
.off('change.orderItems', '.js-order-transport-row select')
47+
.on('change.orderItems', '.js-order-transport-row select', event => this.prefillTransport(event));
48+
this.$card
49+
.off('change.orderItems', '.js-order-payment-row select')
50+
.on('change.orderItems', '.js-order-payment-row select', event => this.prefillPayment(event));
51+
}
2952

30-
if ($items.length === 1) {
31-
const $orderItemRemoveButton = $items.find('.js-order-item-remove');
53+
onFormChange(event) {
54+
const $target = $(event.target);
3255

33-
$orderItemRemoveButton.addClass(OrderItems.textDisabledClass);
56+
if ($target.is('.js-order-transport-row select, .js-order-payment-row select')) {
57+
return;
58+
}
3459

35-
this.tooltip = new Tooltip($orderItemRemoveButton, {
36-
title: Translator.trans('Order must contain at least one item'),
37-
});
38-
} else {
39-
$items.find('.js-order-item-remove').removeClass(OrderItems.textDisabledClass);
60+
FormChangeInfo.showInfo();
61+
}
4062

41-
if (this.tooltip) {
42-
this.tooltip.dispose();
43-
this.tooltip = null;
63+
initializeProductPickers() {
64+
this.$card.find('.js-order-items-add-product').each((_, element) => {
65+
if (element.dataset.orderItemsProductPickerInitialized === '1') {
66+
return;
4467
}
68+
69+
element.dataset.orderItemsProductPickerInitialized = '1';
70+
// eslint-disable-next-line no-new
71+
new ProductPicker($(element), async productId => {
72+
if (await this.runLiveAction('addProduct', { productId: Number(productId) })) {
73+
FormChangeInfo.showInfo();
74+
}
75+
});
76+
});
77+
}
78+
79+
async addItem(event) {
80+
event.preventDefault();
81+
82+
if (await this.runLiveAction('addItem')) {
83+
FormChangeInfo.showInfo();
4584
}
4685
}
4786

48-
addProduct(productId, _productName) {
49-
const $collection = $('#js-order-items');
50-
Ajax.ajax({
51-
url: $collection.data('order-product-add-url'),
52-
method: 'POST',
53-
data: {
54-
productId: productId,
55-
},
56-
success: data => {
57-
const $data = $($.parseHTML(data));
87+
async prefillTransport(event) {
88+
if (await this.runLiveAction('prefillTransport', { transportId: Number($(event.currentTarget).val()) })) {
89+
FormChangeInfo.showInfo();
90+
}
91+
}
5892

59-
const $orderItem = $data.filter('.js-order-item');
93+
async prefillPayment(event) {
94+
if (await this.runLiveAction('prefillPayment', { paymentId: Number($(event.currentTarget).val()) })) {
95+
FormChangeInfo.showInfo();
96+
}
97+
}
6098

61-
$collection.append($orderItem);
62-
new Register().registerNewContent($orderItem);
63-
FormChangeInfo.showInfo();
99+
onRemoveItemClick(event) {
100+
event.preventDefault();
64101

65-
this.refreshCount($collection);
102+
const $removeButton = $(event.currentTarget);
66103

67-
// eslint-disable-next-line no-new
68-
new ModalWindow({
69-
content: Translator.trans('Product saved in order'),
70-
});
71-
},
72-
error: () => {
73-
// eslint-disable-next-line no-new
74-
new ModalWindow({
75-
content: Translator.trans('Unable to add product'),
76-
});
104+
if (this.pendingAction || $removeButton.hasClass('link-disabled') || $removeButton.hasClass('disabled')) {
105+
return;
106+
}
107+
108+
const itemName = escapeHtml($removeButton.closest('.js-order-item').find('.js-order-item-name').val());
109+
110+
ConfirmWindow.show({
111+
content: Translator.trans('Do you really want to remove item "<i>%itemName%</i>" from the order?', {
112+
itemName: itemName,
113+
}),
114+
continueEvent: async () => {
115+
if (
116+
!(await this.runLiveAction('removeItem', {
117+
itemIndex: $removeButton.data('order-item-index').toString(),
118+
}))
119+
) {
120+
return;
121+
}
122+
123+
FormChangeInfo.showInfo();
77124
},
78125
});
79126
}
80127

81-
onRemoveItemClick(event) {
82-
if (!$(event.currentTarget).hasClass(OrderItems.textDisabledClass)) {
83-
const $item = $(event.currentTarget).closest('.js-order-item');
84-
const $itemNameElement = $item.find('.js-order-item-name');
85-
const itemName = escapeHtml($itemNameElement.val());
86-
87-
ConfirmWindow.show({
88-
content: Translator.trans('Do you really want to remove item "<i>%itemName%</i>" from the order?', {
89-
itemName: itemName,
90-
}),
91-
continueEvent: () => {
92-
this.removeItem($item);
93-
},
94-
});
128+
async runLiveAction(actionName, actionArgs = {}) {
129+
if (this.pendingAction) {
130+
return false;
95131
}
96-
event.preventDefault();
97-
}
98132

99-
removeItem($item) {
100-
const $collection = $item.closest('#js-order-items');
133+
this.pendingAction = true;
101134

102-
$item.remove();
135+
try {
136+
const component = await this.getLiveComponent();
103137

104-
FormChangeInfo.showInfo();
105-
this.refreshCount($collection);
138+
await component.action(actionName, actionArgs);
139+
140+
return true;
141+
} finally {
142+
this.pendingAction = false;
143+
}
106144
}
107145

108-
onAddItemClick() {
109-
const $collection = $('#js-order-items');
146+
getLiveComponent() {
147+
if (this.liveComponentPromise === null) {
148+
this.liveComponentPromise = getComponent(this.$liveComponent[0]);
149+
}
110150

111-
this.addItem($collection);
151+
return this.liveComponentPromise;
112152
}
113153

114-
addItem($collection) {
115-
const prototype = $collection.data('prototype');
116-
const index = this.getNewIndex($collection);
154+
async registerLiveRenderHook() {
155+
const component = await this.getLiveComponent();
117156

118-
const item = prototype.replace(/__name__/g, index);
119-
const $item = $($.parseHTML(item));
120-
$item.data('index', index);
157+
if (OrderItems.liveComponentsWithRenderHook.has(component.element)) {
158+
return;
159+
}
121160

122-
$collection.append($item);
123-
new Register().registerNewContent($item);
124-
FormChangeInfo.showInfo();
161+
component.on('render:started', () => {
162+
const $orderItemsCard = $(component.element).find('.js-order-items-card');
163+
OrderItems.disposeSelects($orderItemsCard);
164+
OrderItems.disposeTooltips($orderItemsCard);
165+
});
166+
component.on('render:finished', () => {
167+
new Register().registerNewContent($(component.element));
168+
});
169+
OrderItems.liveComponentsWithRenderHook.add(component.element);
170+
}
171+
172+
static onPriceCalculationChange($orderItem) {
173+
const setPricesManually = $orderItem.find('.js-set-prices-manually').is(':checked');
174+
const $settingPricesManuallyWarning = $orderItem.find('.js-setting-prices-manually-warning');
175+
176+
$orderItem.find('.js-calculable-price').prop('readonly', !setPricesManually);
177+
178+
if (setPricesManually) {
179+
$settingPricesManuallyWarning.removeClass('d-none');
180+
OrderItems.initializeTooltips($settingPricesManuallyWarning);
125181

126-
this.refreshCount($collection);
182+
return;
183+
}
184+
185+
OrderItems.disposeTooltips($settingPricesManuallyWarning);
186+
$settingPricesManuallyWarning.addClass('d-none');
127187
}
128188

129-
getNewIndex($collection) {
130-
let maxIndex = 0;
131-
const newItemIndex = 'new_';
189+
static disposeTooltips($container) {
190+
$container.filterAllNodes('[data-bs-toggle="tooltip"]').each(function () {
191+
Tooltip.getInstance(this)?.dispose();
192+
});
193+
}
132194

133-
$collection.find('.js-order-item').each(function () {
134-
const indexStr = $(this).data('index').toString();
135-
if (indexStr.indexOf(newItemIndex) === 0) {
136-
const index = parseInt(indexStr.slice(4), 10);
137-
if (index > maxIndex) {
138-
maxIndex = index;
139-
}
195+
static disposeSelects($container) {
196+
$container.filterAllNodes('select').each(function () {
197+
this.tomselect?.destroy();
198+
});
199+
}
200+
201+
static initializeTooltips($container) {
202+
$container.filterAllNodes('[data-bs-toggle="tooltip"]').each(function () {
203+
Tooltip.getInstance(this)?.dispose();
204+
205+
const originalTitle = this.getAttribute('data-bs-original-title');
206+
207+
if (!this.getAttribute('title') && originalTitle) {
208+
this.setAttribute('title', originalTitle);
140209
}
210+
211+
new Tooltip(this);
141212
});
213+
}
214+
215+
static initializeDynamicElements($container) {
216+
OrderItems.initializeTooltips($container);
142217

143-
return newItemIndex + (maxIndex + 1);
218+
$container.filterAllNodes('.js-order-item-any').each(function () {
219+
OrderItems.onPriceCalculationChange($(this));
220+
});
144221
}
145222

146-
static onPriceCalculationChange($orderItem) {
147-
const setPricesManually = $orderItem.find('.js-set-prices-manually').is(':checked');
223+
static bindSavedEvent() {
224+
if (OrderItems.savedEventBound) {
225+
return;
226+
}
148227

149-
$orderItem.find('.js-calculable-price').prop('readonly', !setPricesManually);
150-
$orderItem.find('.js-setting-prices-manually-warning').css('display', setPricesManually ? 'block' : 'none');
228+
window.addEventListener('order-detail-items:saved', () => {
229+
FormChangeInfo.removeInfo();
230+
});
231+
window.addEventListener('order-detail-items:cancelled', () => {
232+
FormChangeInfo.removeInfo();
233+
});
234+
OrderItems.savedEventBound = true;
151235
}
152236

153237
static init($container) {
154-
// eslint-disable-next-line no-new
155-
new OrderItems($container);
238+
OrderItems.bindSavedEvent();
156239

157-
$container.filterAllNodes('.js-order-item-any').each(function () {
158-
const $orderItem = $(this);
240+
if ($container.filterAllNodes('.js-order-items-card').length === 0) {
241+
return;
242+
}
159243

160-
OrderItems.onPriceCalculationChange($orderItem);
161-
$orderItem.find('.js-set-prices-manually').change(() => {
162-
OrderItems.onPriceCalculationChange($orderItem);
163-
});
164-
});
244+
// eslint-disable-next-line no-new
245+
new OrderItems($container);
165246
}
166247
}
167248

0 commit comments

Comments
 (0)