Subida del módulo y tema de PrestaShop

This commit is contained in:
Kaloyan
2026-04-09 18:31:51 +02:00
parent 12c253296f
commit 16b3ff9424
39262 changed files with 7418797 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
const discountContainer = '.discount-container';
export default {
currencySelect: '#discount_value_reduction_currency',
currencySelectContainer: `${discountContainer} .price-reduction-currency-selector`,
discountContainer,
includeTaxInput: '#discount_value_reduction_include_tax',
reductionTypeSelect: '#discount_value_reduction_type',
reductionValueSymbol: `${discountContainer} .price-reduction-value .input-group .input-group-append .input-group-text,
${discountContainer} .price-reduction-value .input-group .input-group-prepend .input-group-text`,
freeGiftProductSearchContainer: '#discount_free_gift',
discountTypeRadios: '#discount_type_selector_discount_type_selector input[type="radio"]',
discountTypeSubmit: '#discountTypeSubmit',
specificProductsSearchContainer: '#discount_conditions_product_specific_products',
specificProductItem: '.specific-product-item',
specificProductId: '.specific-product-id',
specificProductType: '.specific-product-type',
specificCombinationId: '.specific-combination-choice',
carriersSelect: '#discount_conditions_delivery_carriers',
countriesSelect: '#discount_conditions_delivery_country',
categoryTree: '#discount_conditions_product_product_segment_category',
customerSearchContainer: '#discount_customer_eligibility_eligibility_single_customer',
productSegmentAttributes: '#discount_conditions_product_product_segment_attributes',
productSegmentFeatures: '#discount_conditions_product_product_segment_features',
quantityPerCustomerInput: '#discount_usability_quantity_per_customer',
customerEligibilityInput: '#discount_customer_eligibility_eligibility',
};

View File

@@ -0,0 +1,27 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import EntitySearchInput from '@components/entity-search-input';
import DiscountMap from '@pages/discount/discount-map';
const {$} = window;
export default class CreateFreeGiftDiscount {
$freeGiftSearchInput: JQuery;
entitySearchInput!: EntitySearchInput;
constructor() {
this.$freeGiftSearchInput = $(DiscountMap.freeGiftProductSearchContainer);
if (this.$freeGiftSearchInput.length) {
const autocompleteUrl = (document.querySelector(DiscountMap.freeGiftProductSearchContainer) as HTMLElement)
?.dataset.remoteUrl;
this.entitySearchInput = new EntitySearchInput(
this.$freeGiftSearchInput,
{remoteUrl: autocompleteUrl},
);
}
}
}

View File

@@ -0,0 +1,113 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import PriceReductionManager from '@components/form/price-reduction-manager';
import DiscountMap from '@pages/discount/discount-map';
import CreateFreeGiftDiscount from '@pages/discount/form/create-free-gift-discount';
import SpecificProducts from '@pages/discount/form/specific-products';
import initGroupedItemCollection from '@PSVue/components/grouped-item-collection';
import {getAllAttributeGroups, getAllFeatureGroups} from '@pages/discount/form/services';
import CustomerSearchInput from '@components/form/customer-search-input';
$(() => {
window.prestashop.component.initComponents(
[
'TranslatableInput',
'ToggleChildrenChoice',
'GeneratableInput',
'ChoiceTree',
'ChoiceTable',
'EventEmitter',
'DisablingSwitch',
],
);
new CreateFreeGiftDiscount();
new SpecificProducts();
// Initialize customer search for single customer eligibility
if ($(DiscountMap.customerSearchContainer).length > 0) {
new CustomerSearchInput(
DiscountMap.customerSearchContainer,
'.js-customer-item',
() => null,
);
}
const reductionTypeSelect = document.querySelector(DiscountMap.reductionTypeSelect);
if (reductionTypeSelect) {
reductionTypeSelect.addEventListener('change', toggleCurrency);
new PriceReductionManager(
DiscountMap.reductionTypeSelect,
DiscountMap.includeTaxInput,
DiscountMap.currencySelect,
DiscountMap.reductionValueSymbol,
DiscountMap.currencySelectContainer,
);
toggleCurrency();
}
const {eventEmitter} = window.prestashop.instance;
eventEmitter.on('ToggleChildrenChoice:toggled', (radio: HTMLInputElement) => {
// We need to trigger change those select2 elements because the component is not loaded when the page is displayed
// if we don't trigger change them, the placeholder cannot be loaded correctly.
if (radio.value === 'country') {
$(DiscountMap.countriesSelect).trigger('change');
}
if (radio.value === 'carriers') {
$(DiscountMap.carriersSelect).trigger('change');
}
if (radio.value === 'single_customer') {
$(DiscountMap.quantityPerCustomerInput).parents('.form-group').hide();
}
if (radio.value === 'customer_groups' || radio.value === 'all_customers') {
$(DiscountMap.quantityPerCustomerInput).parents('.form-group').show();
}
});
if ($(DiscountMap.customerEligibilityInput).find('input[type="radio"]:checked').attr('value') === 'single_customer') {
$(DiscountMap.quantityPerCustomerInput).parents('.form-group').hide();
} else {
$(DiscountMap.quantityPerCustomerInput).parents('.form-group').show();
}
$(DiscountMap.countriesSelect).select2({
templateResult: formatOption,
templateSelection: formatOption,
theme: 'bootstrap4',
});
$(DiscountMap.carriersSelect).select2({
templateResult: formatOption,
templateSelection: formatOption,
theme: 'bootstrap4',
});
function formatOption(option: any) {
if (!option.element || !option.element.dataset.logo) {
return option.text;
}
const imageUrl = option.element.dataset.logo;
return $(
`<span><img src="${imageUrl}"/> ${option.text} </span>`,
);
}
function toggleCurrency(): void {
if ($(DiscountMap.reductionTypeSelect).val() === 'percentage') {
$(DiscountMap.currencySelect).fadeOut();
} else {
$(DiscountMap.currencySelect).fadeIn();
}
}
new window.prestashop.component.ChoiceTree(DiscountMap.categoryTree);
initGroupedItemCollection(DiscountMap.productSegmentAttributes, getAllAttributeGroups);
initGroupedItemCollection(DiscountMap.productSegmentFeatures, getAllFeatureGroups);
});

View File

@@ -0,0 +1,106 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import Router from '@components/router';
import {Item, ItemGroup} from '@PSVue/components/grouped-item-collection/types';
const router = new Router();
const {$} = window;
interface AttributeGroup {
id: number;
name: string;
attributes: Attribute[];
}
interface Attribute {
id: number;
name: string;
color: string | null;
texture: string | null;
}
interface FeatureGroup {
id: number;
name: string;
feature_values: FeatureValue[];
}
interface FeatureValue {
id: number;
name: string;
}
/**
* Fetch all attribute groups and convert them into ItemGroup/Item expected by the
* GroupedItemCollection component.
*/
export const getAllAttributeGroups = async (): Promise<Array<ItemGroup>> => {
const attributeGroups: AttributeGroup[] = await $.get(router.generate('admin_all_attribute_groups'));
const itemGroups: ItemGroup[] = [];
// Transform attribute groups into ItemGroup
attributeGroups.forEach((attributeGroup: AttributeGroup) => {
const itemGroup: ItemGroup = {
id: attributeGroup.id,
name: attributeGroup.name,
items: [],
};
attributeGroup.attributes.forEach((attribute: Attribute) => {
const item: Item = {
id: attribute.id,
name: attribute.name,
selected: false,
groupId: itemGroup.id,
groupName: itemGroup.name,
color: attribute.color,
texture: attribute.texture,
};
itemGroup.items.push(item);
});
itemGroups.push(itemGroup);
});
return itemGroups;
};
/**
* Fetch all feature groups and convert them into ItemGroup/Item expected by the
* GroupedItemCollection component.
*/
export const getAllFeatureGroups = async (): Promise<Array<ItemGroup>> => {
const featureGroups: FeatureGroup[] = await $.get(router.generate('admin_all_feature_groups'));
const itemGroups: ItemGroup[] = [];
// Transform feature groups into ItemGroup
featureGroups.forEach((featureGroup: FeatureGroup) => {
const itemGroup: ItemGroup = {
id: featureGroup.id,
name: featureGroup.name,
items: [],
};
featureGroup.feature_values.forEach((featureValue: FeatureValue) => {
const item: Item = {
id: featureValue.id,
name: featureValue.name,
selected: false,
groupId: itemGroup.id,
groupName: itemGroup.name,
color: null,
texture: null,
};
itemGroup.items.push(item);
});
itemGroups.push(itemGroup);
});
return itemGroups;
};
export default {
getAllAttributeGroups,
getAllFeatureGroups,
};

View File

@@ -0,0 +1,112 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import EntitySearchInput from '@components/entity-search-input';
import DiscountMap from '@pages/discount/discount-map';
import Router from '@components/router';
interface CombinationChoice {
combinationId: number,
combinationName: string,
}
interface ProductCombinationsResult {
combinations: CombinationChoice[]
}
interface jQuerySelect2Choice {
id: number,
text: string,
}
interface jQuerySelect2Results {
results: jQuerySelect2Choice[]
}
const {$} = window;
export default class SpecificProducts {
private $specificProductsSearchInput: JQuery;
private entitySearchInput!: EntitySearchInput;
private router: Router;
constructor() {
this.router = new Router();
this.$specificProductsSearchInput = $(DiscountMap.specificProductsSearchContainer);
this.init();
}
init(): void {
if (this.$specificProductsSearchInput.length) {
const autocompleteUrl = (document.querySelector(DiscountMap.specificProductsSearchContainer) as HTMLElement)
?.dataset.remoteUrl;
this.entitySearchInput = new EntitySearchInput(
this.$specificProductsSearchInput,
{
remoteUrl: autocompleteUrl,
onSelectedContent: ($node: JQuery, item: any) => {
const $combinationIdSelect = $(DiscountMap.specificCombinationId, $node);
if (item.product_type === 'combinations') {
this.initCombinationSelector($combinationIdSelect, item.id);
}
},
filterSelected: false,
},
);
}
// Init specific products already present
$(DiscountMap.specificProductItem).each((index: number, element: Element) => {
const productId = Number(element.querySelector<HTMLInputElement>(DiscountMap.specificProductId)?.value);
const productType = String(element.querySelector<HTMLInputElement>(DiscountMap.specificProductType)?.value);
const $combinationIdSelect = $(DiscountMap.specificCombinationId, element);
if (productType === 'combinations') {
this.initCombinationSelector($combinationIdSelect, productId);
}
});
}
initCombinationSelector($combinationIdSelect: JQuery, productId: number): void {
const limit = $combinationIdSelect.data('minimum-results-for-search');
$combinationIdSelect.removeClass('d-none');
$combinationIdSelect.select2({
minimumResultsForSearch: limit,
ajax: {
url: () => this.router.generate('admin_products_search_product_combinations', {productId, limit}),
dataType: 'json',
type: 'GET',
delay: 250,
data(params: Record<string, string>): Record<string, string> {
return {
q: params.term,
};
},
processResults(data: ProductCombinationsResult): jQuerySelect2Results {
// prepend the "all combinations" choice to the top of the list
const allCombinationsChoice: CombinationChoice = {
combinationId: Number($combinationIdSelect.data('allCombinationsValue')),
combinationName: String($combinationIdSelect.data('allCombinationsLabel')),
};
const results = <jQuerySelect2Choice[]> [{
id: allCombinationsChoice.combinationId,
text: allCombinationsChoice.combinationName,
}];
results.push(...data.combinations.map((combination: CombinationChoice) => ({
id: combination.combinationId,
text: combination.combinationName,
})));
return {results};
},
},
});
}
}

View File

@@ -0,0 +1,35 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import DiscountMap from '@pages/discount/discount-map';
$(() => {
const discountGrid = new window.prestashop.component.Grid('discount');
discountGrid.addExtension(new window.prestashop.component.GridExtensions.ExportToSqlManagerExtension());
discountGrid.addExtension(new window.prestashop.component.GridExtensions.ReloadListExtension());
discountGrid.addExtension(new window.prestashop.component.GridExtensions.SortingExtension());
discountGrid.addExtension(new window.prestashop.component.GridExtensions.FiltersResetExtension());
discountGrid.addExtension(new window.prestashop.component.GridExtensions.ColumnTogglingExtension());
discountGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitRowActionExtension());
discountGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitBulkActionExtension());
discountGrid.addExtension(new window.prestashop.component.GridExtensions.BulkActionCheckboxExtension());
discountGrid.addExtension(new window.prestashop.component.GridExtensions.FiltersSubmitButtonEnablerExtension());
new window.prestashop.component.GridExtensions.FilterLinkGroup();
// Check the type selected and update the submit button state
const discountTypeRadios = document.querySelectorAll<HTMLInputElement>(DiscountMap.discountTypeRadios);
discountTypeRadios.forEach((discountTypeRadio: HTMLInputElement) => {
discountTypeRadio.addEventListener('change', () => {
const discountTypeSubmit = document.querySelector<HTMLInputElement>(DiscountMap.discountTypeSubmit);
if (discountTypeSubmit) {
discountTypeSubmit.disabled = discountTypeRadio.value === '';
discountTypeSubmit.classList.toggle('disabled', discountTypeRadio.value === '');
}
});
});
});