Skip to content

Commit

Permalink
Merge pull request PrestaShop#36655 from boherm/PrestaShop#36260-ui-c…
Browse files Browse the repository at this point in the history
…arrier-price-range

[Carrier] UI Ranges Part 2
  • Loading branch information
jolelievre authored Aug 20, 2024
2 parents 8a79241 + 6aa9c20 commit 1f26035
Show file tree
Hide file tree
Showing 25 changed files with 1,140 additions and 142 deletions.
2 changes: 0 additions & 2 deletions admin-dev/themes/new-theme/js/app/utils/init-components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ import Grid from '@components/grid/grid';
import ModifyAllShopsCheckbox from '@components/modify-all-shops-checkbox';
import MultipleChoiceTable from '@js/components/multiple-choice-table';
import MultistoreConfigField from '@js/components/form/multistore-config-field';
import CarrierRanges from '@js/components/form/carrier-ranges';
import PreviewOpener from '@components/form/preview-opener';
import Router from '@components/router';
import ShopSelector from '@components/shop-selector/shop-selector';
Expand Down Expand Up @@ -166,7 +165,6 @@ const initPrestashopComponents = (): void => {
TranslatableInput,
EntitySearchInput,
EmailInput,
CarrierRanges,
MultipleZoneChoice,
};
};
Expand Down
3 changes: 0 additions & 3 deletions admin-dev/themes/new-theme/js/components/components-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,4 @@ export default {
emailInput: {
inputSelector: '.email-input',
},
carrierRanges: {
addRangeButton: '.js-add-carrier-ranges-btn',
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,6 @@
*/
export default {
openRangeSelectionModal: 'openRangeSelectionModal',
shippingMethodChange: 'carrierShippingMethodChange',
rangesUpdated: 'carrierRangesUpdated',
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <[email protected]>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/

import {EventEmitter} from 'events';
import CarrierFormMap from '@pages/carrier/form/carrier-form-map';
import CarrierFormEventMap from '@pages/carrier/form/carrier-form-event-map';
import ConfirmModal from '@js/components/modal/confirm-modal';
import {Range} from '@pages/carrier/form/types';

const {$} = window;

/**
* This component is used in carrier form page to manage the behavior of the form:
* - Selections of zones, ranges and ranges prices
* - Update form when the carrier shipping method change
*/
export default class CarrierFormManager {
eventEmitter: EventEmitter;

currentShippingSymbol: string;

$zonesInput: JQuery;

$rangesInput: JQuery;

$shippingMethodInput: JQuery;

$freeShippingInput: JQuery;

/**
* @param {EventEmitter} eventEmitter
*/
constructor(eventEmitter: EventEmitter) {
this.eventEmitter = eventEmitter;
this.currentShippingSymbol = '';

// Initialize dom elements
this.$zonesInput = $(CarrierFormMap.zonesInput);
this.$rangesInput = $(CarrierFormMap.rangesInput);
this.$shippingMethodInput = $(CarrierFormMap.shippingMethodInput);
this.$freeShippingInput = $(CarrierFormMap.freeShippingInput);

// Initialize form
this.initForm();

// Initialize listeners
this.initListeners();
}

private initForm() {
// First toggle shipping related controls
this.refreshFreeShipping();
// Then, we need to refresh the shipping method symbol
this.refreshCurrentShippingSymbol();
}

private initListeners() {
this.$zonesInput.on('change', () => this.onChangeZones());
this.$freeShippingInput.on('change', () => this.refreshFreeShipping());
this.$shippingMethodInput.on('change', () => this.refreshCurrentShippingSymbol());
$(CarrierFormMap.zonesContainer).on('click', CarrierFormMap.deleteZoneButton, (e:Event) => this.onDeleteZone(e));
this.eventEmitter.on(CarrierFormEventMap.rangesUpdated, (ranges: Range[]) => this.onChangeRanges(ranges));
}

private refreshFreeShipping(): void {
const isFreeShipping = $(`${CarrierFormMap.freeShippingInput}:checked`).val() === '1';
CarrierFormMap.shippingControls.forEach((inputId: string) => {
const $inputGroup = $(inputId).closest('.form-group');
$inputGroup.toggleClass('d-none', isFreeShipping);
});
}

private refreshCurrentShippingSymbol() {
// First, we need to get the units of the selected shipping method
const shippingMethodUnits = $(CarrierFormMap.shippingMethodRow).data('units');
const shippingMethodValue = <number> this.$shippingMethodInput.filter(':checked').first().val() || -1;
this.currentShippingSymbol = shippingMethodUnits[shippingMethodValue] || '?';

// Then, we need to emit an event to update this symbol to other components
this.eventEmitter.emit(CarrierFormEventMap.shippingMethodChange, this.currentShippingSymbol);

// Finally, we need to update the ranges names with the new symbol
$(CarrierFormMap.rangeRow).each((_, rangeRow: HTMLElement) => {
const $rangeRow = $(rangeRow);
const $rangeName = $rangeRow.find(CarrierFormMap.rangeNamePreview);
const $rangeNameHidden = $rangeRow.find(CarrierFormMap.rangeNameInput);
const from = $rangeRow.find(CarrierFormMap.rangeFromInput).val();
const to = $rangeRow.find(CarrierFormMap.rangeToInput).val();
const rangeName = `${from}${this.currentShippingSymbol} - ${to}${this.currentShippingSymbol}`;
$rangeName.text(rangeName);
$rangeNameHidden.val(rangeName);
});
}

private onChangeZones() {
// First, we retrieve the zones actually displayed and selected
const $zonesContainer = $(CarrierFormMap.zonesContainer);
const $zonesRows = $(CarrierFormMap.zoneRow);
const zones = <string[]> this.$zonesInput.val() ?? [];

// First, we need to delete the zones that are not selected and already displayed
// (and we keep the zones that are already displayed)
const zonesAlreadyDisplayed = <string[]>[];
$zonesRows.each((_, zoneRow: HTMLElement) => {
const $zoneRow = $(zoneRow);
const zoneId = $zoneRow.find(CarrierFormMap.zoneIdInput).val()?.toString();

if (zoneId !== undefined) {
if (!zones.includes(zoneId)) {
$zoneRow.remove();
} else {
zonesAlreadyDisplayed.push(zoneId);
}
}
});

// Then, we need to add the zones that are selected but not displayed
const zonePrototype = $zonesContainer.data('prototype');
zones.forEach((zoneId: string) => {
if (!zonesAlreadyDisplayed.includes(zoneId)) {
// We create new zone row by duplicating the prototype and replacing the zone index
const prototype = zonePrototype.replace(/__zone__/g, $(CarrierFormMap.zoneRow).length);

// We need to update the zone id and the zone name
const $prototype = $(prototype);
$prototype.find(CarrierFormMap.zoneIdInput).val(zoneId);
$prototype.find(CarrierFormMap.zoneNamePreview).text(this.$zonesInput.find(CarrierFormMap.zoneIdOption(zoneId)).text());

// We append the new zone row into the zones container
$zonesContainer.append($prototype);

// Next, we need to prepare the ranges for this zone
const $rangeContainer = $prototype.find(CarrierFormMap.rangesContainer);
const $rangeContainerBody = $prototype.find(CarrierFormMap.rangesContainerBody);
const rangePrototype = $rangeContainer.data('prototype');
// @ts-ignore
const ranges = <Range[]>JSON.parse(this.$rangesInput.val() || '[]');

// For each range selected, we need to create a new range row with the range prototype
ranges.forEach((range: Range, index) => {
// Then, we append the new range row into the range container
const $rPrototype = this.prepareRangePrototype(rangePrototype, index, range);
$rangeContainerBody.append($rPrototype);
});
}
});
}

private onDeleteZone(e: Event) {
e.preventDefault();

// We need to get the zone id to delete
const $currentTarget = $(e.currentTarget as HTMLElement);
const $currentZoneRow = $currentTarget.parents(CarrierFormMap.zoneRow);
const idZoneToDelete = $currentZoneRow.children(CarrierFormMap.zoneIdInput).val();

// We need to display a confirmation modal before deleting the zone
const modal = new ConfirmModal(
{
id: 'modal-confirm-submit-feature-flag',
confirmButtonClass: 'btn-danger',
confirmTitle: $currentTarget.data('modal-title'),
confirmMessage: '',
confirmButtonLabel: $currentTarget.data('modal-confirm'),
closeButtonLabel: $currentTarget.data('modal-cancel'),
},
() => {
// If, the user confirms the deletion, we need to remove the zone
// First, we need to remove this zone from the zones
let zones = <string[]> this.$zonesInput.val() || [];
zones = zones.filter((zoneId: string) => zoneId !== idZoneToDelete);

// And update the zones selected values and trigger the zones selector change event
this.$zonesInput.val(zones);
this.$zonesInput.change();
},
);
modal.show();
}

private onChangeRanges(ranges: Range[]) {
// We retrieve all ranges containers in the page
const $rangesContainerBodies = $(CarrierFormMap.rangesContainerBody);

// For each range container, we need to update the ranges
$rangesContainerBodies.each((_, zoneRangesContainer: HTMLElement) => {
// First, we need to save all values for this range.
const $zoneRangesContainerBody = $(zoneRangesContainer);
const pricesRanges = $(zoneRangesContainer).find(CarrierFormMap.rangeRow).map((__, rangeRow: HTMLElement) => {
const $rangeRow = $(rangeRow);
const from = parseFloat($rangeRow.find(CarrierFormMap.rangeFromInput).val()?.toString() || '0');
const to = parseFloat($rangeRow.find(CarrierFormMap.rangeToInput).val()?.toString() || '0');
const price = $rangeRow.find(CarrierFormMap.rangePriceInput).val() || '';

return {from, to, price};
});

// Then, we reset the ranges container
$zoneRangesContainerBody.html('');

// and, we need to add all the ranges selected
const rangePrototype = $zoneRangesContainerBody.closest(CarrierFormMap.rangesContainer).data('prototype');
ranges.forEach((range: Range, index) => {
// First, we need to prepare the range prototype
const $rPrototype = this.prepareRangePrototype(rangePrototype, index, range);

// Then, we need to search the previous price if exist (oldFrom = newFrom OR oldTo = newTo)
let price = '';

for (let i = 0; i < pricesRanges.length; i += 1) {
if (pricesRanges[i].from === range.from || pricesRanges[i].to === range.to) {
price = pricesRanges[i].price.toString();
break;
}
}

// We set the previous value for this range if it exists
// @ts-ignore
$rPrototype.find(CarrierFormMap.rangePriceInput).val(price);
// Then, we append the new range row into the range container
$zoneRangesContainerBody.append($rPrototype);
});
});
}

private prepareRangePrototype(rangePrototype: string, index: number, range: Range): JQuery {
// We prepare the range prototype by replacing the range index, and setting the range values
const $rPrototype = $(rangePrototype.replace(/__range__/g, index.toString()));
$rPrototype.find(CarrierFormMap.rangeFromInput).val(range.from || '0');
$rPrototype.find(CarrierFormMap.rangeToInput).val(range.to || '0');
$rPrototype.find(CarrierFormMap.rangeNamePreview)
.text(`${range.from}${this.currentShippingSymbol} - ${range.to}${this.currentShippingSymbol}`);

// We return the prototype well formed
return $rPrototype;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <[email protected]>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/

export default {
freeShippingInput: 'input[name="carrier[shipping_settings][is_free]"]',
zonesInput: '#carrier_shipping_settings_ranges_costs_control_zones',
zoneIdOption: (zoneId: number|string): string => `option[value="${zoneId}"]`,
rangesInput: '#carrier_shipping_settings_ranges_costs_control_ranges_data',
rangesSelectionAppId: '#carrier_shipping_settings_ranges-app',
addRangeButton: '.js-add-carrier-ranges-btn',
shippingMethodRow: '#carrier_shipping_settings_shipping_method',
shippingMethodInput: 'input[name="carrier[shipping_settings][shipping_method]"]',
deleteZoneButton: '.js-carrier-delete-zone',
zonesContainer: '#carrier_shipping_settings_ranges_costs',
rangesContainer: '.js-carrier-range-container',
rangesContainerBody: '.js-carrier-range-container-body',
zoneRow: '.js-carrier-zone-row',
zoneIdInput: 'input[name$="[zoneId]"]',
rangeNamePreview: '.js-carrier-range-name .text-preview-value',
rangeNameInput: '.js-carrier-range-name input[type="hidden"]',
rangeRow: '.js-carrier-range-row',
zoneNamePreview: '.card-title .text-preview-value',
rangeFromInput: 'input[name$="[from]"]',
rangeToInput: 'input[name$="[to]"]',
rangePriceInput: 'input[name$="[price]"]',
shippingControls: [
'#carrier_shipping_settings_id_tax_rule_group',
'#carrier_shipping_settings_has_additional_handling_fee',
'#carrier_shipping_settings_shipping_method',
'#carrier_shipping_settings_range_behavior',
'#carrier_shipping_settings_ranges_costs_control',
'#carrier_shipping_settings_ranges_costs',
],
};
Loading

0 comments on commit 1f26035

Please sign in to comment.