1618 lines
57 KiB
JavaScript
1618 lines
57 KiB
JavaScript
///////////////////////////////////////////////////////////
|
||
// TEXT
|
||
// LITERAŁY
|
||
const idmHotspotTextObject = {
|
||
["Kod rabatowy"]: <iai:variable vid="Kod rabatowy"/>,
|
||
["Okazja"]: <iai:variable vid="Okazja"/>,
|
||
["Promocja"]: <iai:variable vid="Promocja"/>,
|
||
["Bestseller"]: <iai:variable vid="Bestseller"/>,
|
||
["Nowość"]: <iai:variable vid="Nowość"/>,
|
||
["Ilość"]: <iai:variable vid="Ilość"/>,
|
||
["Porównaj"]: <iai:variable vid="Porównaj"/>,
|
||
["Dodaj do ulubionych"]: <iai:variable vid="Dodaj do ulubionych"/>,
|
||
["Najniższa cena"]: <iai:variable vid="Najniższa cena"/>,
|
||
["Najniższa cena z 30 dni przed obniżką"]: <iai:variable vid="Najniższa cena z 30 dni przed obniżką"/>,
|
||
["Zwiększ ilość"]: <iai:variable vid="Zwiększ ilość"/>,
|
||
["Zmniejsz ilość"]: <iai:variable vid="Zmniejsz ilość"/>,
|
||
["Najniższa cena produktu w okresie 30 dni przed wprowadzeniem obniżki"]: <iai:variable vid="Najniższa cena produktu w okresie 30 dni przed wprowadzeniem obniżki"/>,
|
||
["Cena regularna"]: <iai:variable vid="Cena regularna"/>,
|
||
["Cena bez kodu"]: <iai:variable vid="Cena bez kodu"/>,
|
||
["Cena nadchodząca od"]: <iai:variable vid="Cena nadchodząca od"/>,
|
||
["Coś poszło nie tak podczas dodawania do koszyka. Spróbuj ponownie lub odśwież stronę"]: <iai:variable vid="Coś poszło nie tak podczas dodawania do koszyka. Spróbuj ponownie lub odśwież stronę"/>,
|
||
["Nie znaleziono produktów"]: <iai:variable vid="Nie znaleziono produktów"/>,
|
||
["Błąd przy pobieraniu danych"]: <iai:variable vid="Błąd przy pobieraniu danych"/>,
|
||
["Kliknij, by przejść do formularza kontaktu"]: <iai:variable vid="Kliknij, by przejść do formularza kontaktu"/>,
|
||
["Cena na telefon"]: <iai:variable vid="Cena na telefon"/>,
|
||
["Dodany"]: <iai:variable vid="Dodany"/>,
|
||
["Wystąpił błąd"]: <iai:variable vid="Wystąpił błąd"/>,
|
||
["Do koszyka"]: <iai:variable vid="Do koszyka"/>,
|
||
["Maksymalna liczba sztuk tego towaru które możesz dodać do koszyka to:"]: <iai:variable vid="Maksymalna liczba sztuk tego towaru które możesz dodać do koszyka to:"/>,
|
||
["Minimalna liczba sztuk tego towaru które możesz dodać do koszyka to:"]: <iai:variable vid="Minimalna liczba sztuk tego towaru które możesz dodać do koszyka to:"/>,
|
||
["Wystąpił błąd z inicjalizacją. Proszę odśwież stronę"]: <iai:variable vid="Wystąpił błąd z inicjalizacją. Proszę odśwież stronę"/>,
|
||
["Nie znaleziono kontenera"]: <iai:variable vid="Nie znaleziono kontenera"/>,
|
||
["Nie znaleziono metody graphql"]: <iai:variable vid="Nie znaleziono metody graphql"/>,
|
||
["Drugie Zdjęcie"]: <iai:variable vid="Drugie Zdjęcie"/>,
|
||
["Zobacz więcej"]: <iai:variable vid="Zobacz więcej"/>,
|
||
};
|
||
|
||
// STRING
|
||
// const idmHotspotTextObject = {
|
||
// "Kod rabatowy": "Kod rabatowy",
|
||
// "Okazja": "Okazja",
|
||
// "Promocja": "Promocja",
|
||
// "Bestseller": "Bestseller",
|
||
// "Nowość": "Nowość",
|
||
// "Ilość": "Ilość",
|
||
// "Porównaj": "Porównaj",
|
||
// "Dodaj do ulubionych": "Dodaj do ulubionych",
|
||
// "Najniższa cena": "Najniższa cena",
|
||
// "Najniższa cena z 30 dni przed obniżką": "Najniższa cena z 30 dni przed obniżką",
|
||
// "Zwiększ ilość": "Zwiększ ilość",
|
||
// "Zmniejsz ilość": "Zmniejsz ilość",
|
||
// "Najniższa cena produktu w okresie 30 dni przed wprowadzeniem obniżki":
|
||
// "Najniższa cena produktu w okresie 30 dni przed wprowadzeniem obniżki",
|
||
// "Cena regularna": "Cena regularna",
|
||
// "Cena bez kodu": "Cena bez kodu",
|
||
// "Cena nadchodząca od": "Cena nadchodząca od",
|
||
// "Coś poszło nie tak podczas dodawania do koszyka. Spróbuj ponownie lub odśwież stronę":
|
||
// "Coś poszło nie tak podczas dodawania do koszyka. Spróbuj ponownie lub odśwież stronę",
|
||
// "Nie znaleziono produktów": "Nie znaleziono produktów",
|
||
// "Błąd przy pobieraniu danych": "Błąd przy pobieraniu danych",
|
||
// "Kliknij, by przejść do formularza kontaktu":
|
||
// "Kliknij, by przejść do formularza kontaktu",
|
||
// "Cena na telefon": "Cena na telefon",
|
||
// "Dodany": "Dodany",
|
||
// "Wystąpił błąd": "Wystąpił błąd",
|
||
// "Do koszyka": "Do koszyka",
|
||
// "Maksymalna liczba sztuk tego towaru które możesz dodać do koszyka to:":
|
||
// "Maksymalna liczba sztuk tego towaru które możesz dodać do koszyka to:",
|
||
// "Minimalna liczba sztuk tego towaru które możesz dodać do koszyka to:":
|
||
// "Minimalna liczba sztuk tego towaru które możesz dodać do koszyka to:",
|
||
// "Wystąpił błąd z inicjalizacją. Proszę odśwież stronę":
|
||
// "Wystąpił błąd z inicjalizacją. Proszę odśwież stronę",
|
||
// "Nie znaleziono kontenera": "Nie znaleziono kontenera",
|
||
// "Nie znaleziono metody graphql": "Nie znaleziono metody graphql",
|
||
// "Drugie Zdjęcie": "Drugie Zdjęcie",
|
||
// "Zobacz więcej": "Zobacz więcej",
|
||
// };
|
||
|
||
///////////////////////////////////////////////
|
||
// GraphQL
|
||
// ogolne
|
||
const IDM_PRICE_QUERY = `price {
|
||
rebateCodeActive
|
||
price {
|
||
gross {
|
||
value
|
||
formatted
|
||
}
|
||
}
|
||
omnibusPrice {
|
||
gross {
|
||
value
|
||
formatted
|
||
}
|
||
}
|
||
omnibusPriceDetails {
|
||
unit {
|
||
gross {
|
||
value
|
||
formatted
|
||
}
|
||
}
|
||
youSavePercent
|
||
omnibusPriceIsHigherThanSellingPrice
|
||
newPriceEffectiveUntil {
|
||
formatted
|
||
}
|
||
}
|
||
max {
|
||
gross {
|
||
value
|
||
formatted
|
||
}
|
||
}
|
||
unit {
|
||
gross {
|
||
value
|
||
formatted
|
||
}
|
||
}
|
||
unitConvertedPrice {
|
||
gross {
|
||
value
|
||
formatted
|
||
}
|
||
}
|
||
youSavePercent
|
||
beforeRebate {
|
||
gross {
|
||
value
|
||
formatted
|
||
}
|
||
}
|
||
beforeRebateDetails {
|
||
youSavePercent
|
||
unit {
|
||
gross {
|
||
value
|
||
formatted
|
||
}
|
||
}
|
||
}
|
||
advancePrice {
|
||
gross {
|
||
value
|
||
formatted
|
||
}
|
||
}
|
||
suggested {
|
||
gross {
|
||
value
|
||
formatted
|
||
}
|
||
}
|
||
rebateNumber {
|
||
number
|
||
gross {
|
||
value
|
||
formatted
|
||
}
|
||
}
|
||
}`;
|
||
|
||
const IDM_PRODUCT_QUERY = `id
|
||
type
|
||
name
|
||
icon
|
||
iconSecond
|
||
iconSmall
|
||
iconSmallSecond
|
||
link
|
||
zones
|
||
producer{
|
||
name
|
||
}
|
||
category{
|
||
name
|
||
}
|
||
sizes{
|
||
id
|
||
amount
|
||
name
|
||
${IDM_PRICE_QUERY}
|
||
}
|
||
group{
|
||
id
|
||
name
|
||
link
|
||
versions{
|
||
id
|
||
name
|
||
icon
|
||
iconSecond
|
||
iconSmall
|
||
iconSmallSecond
|
||
link
|
||
}
|
||
}
|
||
opinion{
|
||
rating,
|
||
count
|
||
}
|
||
awardedParameters {
|
||
name
|
||
id
|
||
description
|
||
values {
|
||
name
|
||
id
|
||
}
|
||
}
|
||
enclosuresImages {
|
||
position
|
||
url
|
||
}
|
||
points
|
||
unit{
|
||
id, name, singular, plural, fraction, sellBy, precision, unitConvertedFormat
|
||
}
|
||
${IDM_PRICE_QUERY}`;
|
||
// 1. products
|
||
const IDM_PRODUCTS_GQL = (args) => JSON.stringify({
|
||
query: `{
|
||
products(${args}){
|
||
took
|
||
products{
|
||
${IDM_PRODUCT_QUERY}
|
||
}
|
||
}
|
||
}`
|
||
});
|
||
|
||
// 2. hotspots
|
||
const IDM_HOTSPOTS_GQL = (args) => JSON.stringify({
|
||
query: `{
|
||
hotspots(${args}){
|
||
took
|
||
name
|
||
url
|
||
products{
|
||
${IDM_PRODUCT_QUERY}
|
||
}
|
||
}
|
||
}`
|
||
});
|
||
|
||
// 3. single product
|
||
const IDM_PRODUCT_GQL = (args) => JSON.stringify({
|
||
query: `{
|
||
product(${args}){
|
||
product{
|
||
${IDM_PRODUCT_QUERY}
|
||
}
|
||
}
|
||
}`
|
||
});
|
||
// ADD TO BASKET
|
||
const IDM_HOTSPOT_ADD_TO_BASKET = (t, e, a) => JSON.stringify({
|
||
query: `mutation {
|
||
addProductsToBasket(ProductInput: {id: ${t}, size: "${e}", quantity: ${a}}) {
|
||
status
|
||
results {
|
||
status
|
||
error {
|
||
code
|
||
message
|
||
}
|
||
}
|
||
}
|
||
}`
|
||
});
|
||
/////////////////////////////////////////
|
||
// JS
|
||
app_shop.fn.idmGetOmnibusDetails = (options) => {
|
||
const {
|
||
productData, sizeId, priceType = app_shop.vars.priceType,
|
||
} = options || {};
|
||
if (!productData) return false;
|
||
const sizeData = productData.sizes.find((size) => size.id === sizeId) || productData;
|
||
if (!sizeData?.price) return false;
|
||
const classes = {
|
||
add: [],
|
||
remove: ['--omnibus', '--omnibus-short', '--omnibus-code', '--omnibus-code-short', '--omnibus-new-price', '--omnibus-higher'],
|
||
};
|
||
const activeLabel = {};
|
||
|
||
const omnibusPrice = sizeData.price?.omnibusPriceDetails?.unit?.[priceType]?.formatted || sizeData.price.omnibusPrice[priceType]?.formatted;
|
||
if (!omnibusPrice) {
|
||
return {
|
||
classes,
|
||
};
|
||
}
|
||
// Omnibus
|
||
classes.add.push('--omnibus');
|
||
classes.remove = classes.remove.filter((item) => item !== '--omnibus');
|
||
|
||
const sellBy = productData?.unit?.sellBy;
|
||
|
||
const unitMaxPrice = sizeData?.price?.unit?.[priceType]?.formatted && sizeData.price.max?.[priceType]?.value ? format_price(parseFloat(sizeData.price.max?.[priceType]?.value) * parseFloat(sellBy), {
|
||
mask: app_shop.vars.currency_format,
|
||
currency: app_shop.vars?.currency?.symbol,
|
||
currency_space: app_shop.vars.currency_space,
|
||
currency_before_price: app_shop.vars.currency_before_value,
|
||
}) : false;
|
||
|
||
const maxPrice = unitMaxPrice || sizeData.price.max?.[priceType]?.formatted;
|
||
// Skrócona wersja omnibusa
|
||
if (!maxPrice || maxPrice === omnibusPrice) {
|
||
classes.add.push('--omnibus-short');
|
||
classes.remove = classes.remove.filter((item) => item !== '--omnibus-short');
|
||
}
|
||
// Aktywny kod rabatowy
|
||
if (app_shop.vars.omnibus?.rebateCodeActivate && sizeData.price?.rebateCodeActive) {
|
||
classes.add.push('--omnibus-code');
|
||
activeLabel.rebateCodeActive = `<span class="label --code --omnibus">${omnibusDetailsTxt?.['Kod rabatowy']}</span>`;
|
||
classes.remove = classes.remove.filter((item) => item !== '--omnibus-code');
|
||
}
|
||
// Skrócona wersja omnibusa, gdy aktywny kod rabatowy
|
||
const beforeRebatePrice = sizeData.price.beforeRebateDetails?.unit?.[priceType]?.formatted || sizeData.price.beforeRebate[priceType]?.formatted;
|
||
if (app_shop.vars.omnibus?.rebateCodeActivate && beforeRebatePrice === omnibusPrice && sizeData.price?.rebateCodeActive) {
|
||
classes.add.push('--omnibus-code-short');
|
||
classes.remove = classes.remove.filter((item) => item !== '--omnibus-code-short');
|
||
}
|
||
// Nadchodząca cena
|
||
const newDate = sizeData.price.omnibusPriceDetails?.newPriceEffectiveUntil?.formatted;
|
||
if (newDate && maxPrice) {
|
||
classes.add.push('--omnibus-new-price');
|
||
classes.remove = classes.remove.filter((item) => item !== '--omnibus-new-price');
|
||
}
|
||
// Cena omnibusa wyższa niż cena sprzedaży
|
||
const higher = sizeData.price.omnibusPriceDetails?.omnibusPriceIsHigherThanSellingPrice;
|
||
if (higher) {
|
||
classes.add.push('--omnibus-higher');
|
||
classes.remove = classes.remove.filter((item) => item !== '--omnibus-higher');
|
||
}
|
||
// label okazja
|
||
if ((!higher || newDate) && !activeLabel?.rebateCodeActive) {
|
||
activeLabel.bargain = `<span class="label --bargain --omnibus">${omnibusDetailsTxt?.['Okazja']}</span>`;
|
||
}
|
||
// label promocja
|
||
if (Object.keys(activeLabel)?.length === 0) {
|
||
activeLabel.bargain = `<span class="label --promo --omnibus">${omnibusDetailsTxt?.['Promocja']}</span>`;
|
||
}
|
||
|
||
let omnibusPercentSign = '';
|
||
if (higher) {
|
||
omnibusPercentSign = '-';
|
||
} else if (sizeData.price.omnibusPriceDetails?.youSavePercent !== 0) {
|
||
omnibusPercentSign = '+';
|
||
}
|
||
const omnibusPercent = `${omnibusPercentSign}${sizeData.price.omnibusPriceDetails?.youSavePercent}%`;
|
||
const omnibus = {
|
||
price: omnibusPrice,
|
||
visible: true,
|
||
percent: omnibusPercent,
|
||
html: `<span class="omnibus_price__text">${omnibusDetailsTxt['Najniższa cena produktu w okresie 30 dni przed wprowadzeniem obniżki']}: </span><del class="omnibus_price__value">${omnibusPrice}</del><span class="price_percent">${omnibusPercent}</span>`,
|
||
};
|
||
|
||
const max = (maxPrice) ? {
|
||
max: {
|
||
price: maxPrice,
|
||
visible: true,
|
||
percent: `-${sizeData.price.youSavePercent}%`,
|
||
html: `<span class="omnibus_label">${omnibusDetailsTxt['Cena regularna']}: </span>
|
||
<del>${maxPrice}</del><span class="price_percent">-${sizeData.price.youSavePercent}%</span>`,
|
||
},
|
||
} : {};
|
||
|
||
const beforeRebate = (beforeRebatePrice) ? {
|
||
beforeRebate: {
|
||
price: beforeRebatePrice,
|
||
visible: !!classes.add.includes('--omnibus-code'),
|
||
percent: `-${sizeData.price.beforeRebateDetails?.youSavePercent}%`,
|
||
html: `<span class="omnibus_label">${omnibusDetailsTxt['Cena bez kodu']}: </span>
|
||
<del>${beforeRebatePrice}</del>
|
||
<span class="price_percent">-${sizeData.price.beforeRebateDetails?.youSavePercent}%</span>`,
|
||
},
|
||
} : {};
|
||
|
||
const newPriceEffectiveUntil = (newDate) ? {
|
||
newPriceEffectiveUntil: {
|
||
date: newDate,
|
||
price: maxPrice,
|
||
visible: !!classes.add.includes('--omnibus-new-price'),
|
||
html: `<span class="omnibus_label">${omnibusDetailsTxt['Cena nadchodząca od']} </span>
|
||
<span class="new_price__date">${newDate}: </span>
|
||
<span class="new_price__value">${maxPrice}</span>`,
|
||
},
|
||
} : {};
|
||
const omnibusLabel = {
|
||
omnibusLabel: {
|
||
name: Object.keys(activeLabel)?.[0] || '',
|
||
html: activeLabel[Object.keys(activeLabel)?.[0]],
|
||
},
|
||
};
|
||
|
||
return {
|
||
classes,
|
||
omnibus,
|
||
...max,
|
||
...beforeRebate,
|
||
...newPriceEffectiveUntil,
|
||
...omnibusLabel,
|
||
};
|
||
};
|
||
|
||
/**
|
||
* Klasa IdmHotspot
|
||
* ============================
|
||
* Odpowiada za tworzenie, renderowanie i obsługę dynamicznych hotspotów produktowych.
|
||
* Pobiera dane przez GraphQL, renderuje produkty, obsługuje dodawanie do koszyka,
|
||
* inicjuje Swipera, ustawia wysokości, nasłuchuje zdarzeń i wykonuje lazy loading.
|
||
*/
|
||
class IdmHotspot{
|
||
// ============================
|
||
// DOMYŚLNE USTAWIENIA SWIPERA
|
||
// ============================
|
||
static idmDefaultSwiperConfig = { // true, false, obiekt z opcjami swipera
|
||
loop: false,
|
||
autoHeight: false,
|
||
spaceBetween: 16,
|
||
slidesPerView: 1.4,
|
||
centeredSlides: true,
|
||
centeredSlidesBounds: true,
|
||
breakpoints: {
|
||
550: {
|
||
slidesPerView: 3,
|
||
centeredSlides: true,
|
||
centeredSlidesBounds: true,
|
||
},
|
||
979: {
|
||
slidesPerView: 4,
|
||
centeredSlides: false,
|
||
},
|
||
}
|
||
}
|
||
// ============================
|
||
// DOMYŚLNE OPCJE HOTSPOTA
|
||
// ============================
|
||
static idmDefaultHotspotOptions = {
|
||
options: {
|
||
lazy: true,
|
||
devMode: false,
|
||
callbackFn: ()=>{},
|
||
omnibusTooltip: false,
|
||
// switchImage: false,
|
||
|
||
// POKAZANIE
|
||
showOpinions: false,
|
||
showSecondImage: false,
|
||
|
||
// DODAWANIE
|
||
addToBasket: true, // true, false, "range"
|
||
addToFavorites: false, // Wymaga zmian szablonowych
|
||
addToCompare: false,
|
||
|
||
// WYBÓR
|
||
// selectSize: false,
|
||
// selectVersion: false,
|
||
|
||
// SWIPER
|
||
swiper: true,
|
||
swiperScrollbar: false,
|
||
}
|
||
}
|
||
/**
|
||
* Konstruktor
|
||
* @param {object} object - Dane konfiguracyjne hotspotu
|
||
*/
|
||
constructor({id, title, classes, placement, source, query, options = {}, hotspotEl, products}){
|
||
this.id = id || "";
|
||
this.title = title || "";
|
||
this.classes = classes || "";
|
||
this.placement = placement || {};
|
||
this.source = source || {};
|
||
this.query = query || {};
|
||
// this.type = type;
|
||
this.products = products || null;
|
||
|
||
this.hotspotEl = hotspotEl || null;
|
||
|
||
// Merge defaults
|
||
this.options = {
|
||
...IdmHotspot.idmDefaultHotspotOptions.options,
|
||
...options,
|
||
};
|
||
|
||
//
|
||
// this.hotspots = {};
|
||
this.priceType = app_shop?.vars?.priceType || "gross";
|
||
|
||
|
||
// bind this do funkcji eventowych
|
||
this.handleAddToBasket = this.handleAddToBasket.bind(this);
|
||
this.handleQuantityButtonClick = this.handleQuantityButtonClick.bind(this);
|
||
this.handleQuantityInputChange = this.handleQuantityInputChange.bind(this);
|
||
this.handleAddToCompare = this.handleAddToCompare.bind(this);
|
||
this.handleAddToFav = this.handleAddToFav.bind(this);
|
||
this.handleShowSecondImage = this.handleShowSecondImage.bind(this);
|
||
this.handleHideSecondImage = this.handleHideSecondImage.bind(this);
|
||
this.handleSelectVersion = this.handleSelectVersion.bind(this);
|
||
|
||
this.init();
|
||
|
||
|
||
// WCZYTANIE PONOWNIE DLA KOSZYKA
|
||
if(typeof app_shop.fn?.basket?.reloadForm === "function" && this.hotspotEl.closest("#content")){
|
||
app_shop.run(()=>{
|
||
this.init();
|
||
}, "all", "#Basket", true)
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
// ========================================================
|
||
// ASYNC – POBIERANIE DANYCH Z GRAPHQL
|
||
// ========================================================
|
||
/**
|
||
* Przygotowuje funkcję i zapytanie GraphQL w zależności od typu danych.
|
||
*/
|
||
getQueryData(){
|
||
let graphFn, query;
|
||
let queryMarkup = "";
|
||
|
||
if(this.source?.hotspotsType){
|
||
graphFn = IDM_HOTSPOTS_GQL;
|
||
queryMarkup += `hotspot: ${this.source.hotspotsType}, limit: 16`;
|
||
}else{
|
||
graphFn = IDM_PRODUCTS_GQL;
|
||
if(this.source?.productsId){
|
||
queryMarkup += `productsId: [${this.source.productsId}],`;
|
||
}
|
||
if(this.source?.productsMenu){
|
||
queryMarkup += `navigation: ${this.source.productsMenu},`;
|
||
}
|
||
if(this.source?.producersId){
|
||
queryMarkup += `producers: [${this.source.producersId}],`;
|
||
}
|
||
if(this.source?.seriesId){
|
||
queryMarkup += `series: [${this.source.seriesId}],`;
|
||
}
|
||
if(this.source?.parametersId){
|
||
queryMarkup += `parameters: [${this.source.parametersId.reduce((acc,val)=> acc + `{id: ${val}}`,"")}],`;
|
||
}
|
||
if(this.source?.priceRange){
|
||
queryMarkup += `priceRange: {from: ${+this.source.priceRange?.from || 0}, to: ${+this.source.priceRange?.to || 0}},`;
|
||
}
|
||
}
|
||
|
||
query = `searchInput: { ${queryMarkup} }`
|
||
|
||
return [graphFn, query];
|
||
}
|
||
/**
|
||
* Ustawia dane zapytania GraphQL wewnątrz instancji.
|
||
*/
|
||
setQueryData(){
|
||
const [graphFn, queryString] = this.getQueryData();
|
||
this.query.graphFn = graphFn;
|
||
this.query.string = queryString;
|
||
}
|
||
|
||
/**
|
||
* Pobiera dane hotspotu z API GraphQL.
|
||
*/
|
||
async getHotspotData(){
|
||
if(this.products) return;
|
||
try{
|
||
const res = await fetch(`/graphql/v1/`, {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
},
|
||
body: this.query.graphFn(this.query.string),
|
||
});
|
||
const data = await res.json();
|
||
const products = data?.data?.[this.query.graphFn === IDM_HOTSPOTS_GQL ? "hotspots" : "products"]?.products;
|
||
if(!products || !products.length) throw new Error(idmHotspotTextObject["Nie znaleziono produktów"]);
|
||
|
||
this.products = products;
|
||
this.title = this.title || data?.data?.hotspots?.name || "";
|
||
}catch(err){
|
||
console.error(idmHotspotTextObject["Błąd przy pobieraniu danych"], err);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// ========================================================
|
||
// MARKUP – TWORZENIE HTML PRODUKTÓW
|
||
// ========================================================
|
||
|
||
/**
|
||
* Tworzy markup dla wszystkich produktów w hotspotcie.
|
||
*/
|
||
markup(){
|
||
let markup = "";
|
||
this.products.forEach((prod)=>{
|
||
markup += this.markupProduct(prod);
|
||
})
|
||
return markup;
|
||
}
|
||
|
||
/**
|
||
* Tworzy markup dla pojedynczego produktu.
|
||
*/
|
||
markupProduct(prod){
|
||
// markup pojedynczego produktu
|
||
let singleMarkup = "";
|
||
singleMarkup += `
|
||
<div class="product hotspot__product swiper-slide d-flex flex-column ${prod.price.price[this.priceType].value === 0 ? "--phone" : ""}" data-id="${prod.id}">
|
||
${this.markupProductInnerHTML(prod)}
|
||
</div>`;
|
||
|
||
return singleMarkup;
|
||
}
|
||
markupProductInnerHTML(prod){
|
||
// IDM DO POPRAWKI
|
||
const prodExchangedData = app_shop?.fn?.getOmnibusDetails?.({productData: prod}) || app_shop.fn?.idmGetOmnibusDetails({productData: prod});
|
||
|
||
return `<div class="product__yousave --hidden">
|
||
<span class="product__yousave --label"></span>
|
||
<span class="product__yousave --value"></span>
|
||
</div>
|
||
<div class="product__additional">
|
||
${this.markupAdditional(prod)}
|
||
</div>
|
||
<a class="product__icon d-flex justify-content-center align-items-center" tabindex="-1" href="${prod.link}">
|
||
${this.markupImage(prod)}
|
||
<strong class="label_icons">
|
||
${this.markupLabel(prod)}
|
||
</strong>
|
||
</a>
|
||
<div class="product__content_wrapper">
|
||
${this.markupVersions(prod)}
|
||
${this.markupOpinions(prod)}
|
||
<a class="product__name" tabindex="0" href="${prod.link}" title="${prod.name}">${prod.name}</a>
|
||
<div class="product__prices mb-auto ${prodExchangedData?.classes?.add?.reduce((acc,val) => acc + " " + val,"")}">
|
||
${this.markupPrice(prod, prodExchangedData)}
|
||
</div>
|
||
${this.markupAddToBasket(prod)}
|
||
</div>`;
|
||
}
|
||
|
||
markupAdditional(prod){
|
||
return `
|
||
${this.markupCompare(prod)}
|
||
${this.markupFavorite(prod)}
|
||
`;
|
||
}
|
||
|
||
markupCompare(prod){
|
||
if(!this.options?.addToCompare) return "";
|
||
|
||
return `<button class="idm-products-banner__compare-btn" data-compare-id="${prod.id}" aria-label="${idmHotspotTextObject["Porównaj"]}">
|
||
</button>`
|
||
}
|
||
|
||
markupFavorite(prod){
|
||
if(!this.options?.addToFavorites || typeof this.addToFavFn !== "function") return "";
|
||
|
||
return `<span
|
||
data-href="#add_favorite"
|
||
class="product__favorite" role="button" tabindex="0"
|
||
data-product-id="${prod.id}"
|
||
data-product-size="${prod.sizes?.[0]?.id || 'uniw'}" aria-label="${idmHotspotTextObject["Dodaj do ulubionych"]}">
|
||
</span>`;
|
||
}
|
||
|
||
markupVersions(prod){
|
||
if(!this.options?.selectVersion || !prod.group?.versions || prod.group?.versions?.length === 1 ) return "";
|
||
|
||
const MAX_VERION_AMOUNT = 5;
|
||
|
||
const sortedVersions = prod.group.versions.sort(function (a, b) {
|
||
if (a.name < b.name) {
|
||
return -1;
|
||
}
|
||
if (a.name > b.name) {
|
||
return 1;
|
||
}
|
||
return 0;
|
||
})
|
||
|
||
|
||
return `<div class="product__versions">
|
||
${sortedVersions.reduce((acc, val, index)=>{
|
||
if(index + 1 > MAX_VERION_AMOUNT) return acc;
|
||
|
||
if(index + 1 === MAX_VERION_AMOUNT && prod.group.versions.length > MAX_VERION_AMOUNT) return acc + `<a class="product__version_more" href="${prod.link}" aria-label="${idmHotspotTextObject["Zobacz więcej"]}">+${(prod.group.versions.length - MAX_VERION_AMOUNT) + 1}</a>`;
|
||
|
||
return acc + `<a class="product__version_single ${prod.id === val.id ? "--active" : ""}" href="${val.link}" data-product-id="${val.id}"><img class="product__version_img" src="${val.icon}" alt="${val.name}"/></a>`;
|
||
},"")}
|
||
</div>`
|
||
}
|
||
|
||
markupImage(prod){
|
||
let markup = "";
|
||
if(prod.iconSmallSecond && prod.iconSecond) markup +=`<picture>
|
||
<source media="(min-width: 421px)" type="image/webp" srcset="${prod.icon}"/>
|
||
<source media="(min-width: 421px)" type="image/jpeg" srcset="${prod.iconSecond}"/>
|
||
<source media="(max-width: 420px)" type="image/webp" srcset="${prod.iconSmall}"/>
|
||
<source media="(max-width: 420px)" type="image/jpeg" srcset="${prod.iconSmallSecond}"/>
|
||
<img src="${prod.iconSecond}" loading="lazy" alt="${prod.name}" class="product__image --first">
|
||
</picture>`;
|
||
else if(prod?.iconSmall) markup += `<picture>
|
||
<source media="(min-width: 421px)" srcset="${prod.icon}"/>
|
||
<source media="(max-width: 420px)" srcset="${prod.iconSmall}"/>
|
||
<img src="${prod.iconSecond}" loading="lazy" alt="${prod.name}" class="product__image --first">
|
||
</picture>`;
|
||
else markup += `<img src="${prod.icon}" loading="lazy" alt="${prod.name}" class="product__image --first">`;
|
||
|
||
if(this.options?.showSecondImage && prod.enclosuresImages?.[1]?.url) markup += `<img loading="lazy" src="${prod.enclosuresImages[1].url}" alt="${prod.name} - ${idmHotspotTextObject["Drugie Zdjęcie"]}" class="product__image --second"/>`;
|
||
|
||
return markup;
|
||
}
|
||
|
||
markupLabel(prod){
|
||
let labelMarkup = ""
|
||
// labele zones
|
||
if(prod.zones?.find(zone => zone ==="bestseller")) labelMarkup += `<span class="label --bestseller --omnibus">${idmHotspotTextObject["Bestseller"]}</span>`;
|
||
if(prod.zones?.find(zone => zone ==="news")) labelMarkup += `<span class="label --news --omnibus">${idmHotspotTextObject["Nowość"]}</span>`;
|
||
|
||
const omnibusPrice = prod.price?.omnibusPriceDetails?.unit?.[this.idmPriceType]?.formatted || prod.price.omnibusPrice[this.idmPriceType]?.formatted;
|
||
if(omnibusPrice){
|
||
const newDate = prod.price.omnibusPriceDetails?.newPriceEffectiveUntil?.formatted;
|
||
const higher = prod.price.omnibusPriceDetails?.omnibusPriceIsHigherThanSellingPrice;
|
||
if (app_shop.vars.omnibus?.rebateCodeActivate && prod.price?.rebateCodeActive) {
|
||
labelMarkup += `<span class="label --code --omnibus">${idmHotspotTextObject["Kod rabatowy"]}</span>`;
|
||
}else if ((!higher || newDate) && !activeLabel?.rebateCodeActive) {
|
||
labelMarkup += `<span class="label --bargain --omnibus">${idmHotspotTextObject["Okazja"]}</span>`;
|
||
}else {
|
||
labelMarkup += `<span class="label --promo --omnibus">${idmHotspotTextObject["Promocja"]}</span>`;
|
||
}
|
||
}
|
||
|
||
return labelMarkup;
|
||
}
|
||
|
||
markupOpinions(prod){
|
||
if(!this.options?.showOpinions) return "";
|
||
|
||
return `<div class="product__opinions">
|
||
<div class="product__opinions_stars">
|
||
<i class="icon-star ${prod.opinion?.rating > 0.5 ? "--active" : ""}"></i>
|
||
<i class="icon-star ${prod.opinion?.rating > 1.5 ? "--active" : ""}"></i>
|
||
<i class="icon-star ${prod.opinion?.rating > 2.5 ? "--active" : ""}"></i>
|
||
<i class="icon-star ${prod.opinion?.rating > 3.5 ? "--active" : ""}"></i>
|
||
<i class="icon-star ${prod.opinion?.rating > 4.5 ? "--active" : ""}"></i>
|
||
</div>
|
||
<span class="product__opinions_score">${prod.opinion.rating.toFixed(2)} / 5.00</span>
|
||
</div>`
|
||
}
|
||
|
||
markupPrice(prod, prodExchangedData){
|
||
const price = prod.price.price[this.priceType];
|
||
const unit = prod.unit;
|
||
const pointsPrice = prod?.points;
|
||
const convertedPrice = prod.price?.unitConvertedPrice?.[this.priceType]?.formatted;
|
||
|
||
return `
|
||
<strong class="price --normal --main ${price.value === 0 ? "--hidden" : ""}">
|
||
<span class="price__sub">${price.formatted}</span>
|
||
<span class="price_sellby">
|
||
<span class="price_sellby__sep">/</span>
|
||
<span class="price_sellby__sellby" data-sellby="${unit?.sellBy}">${unit?.sellBy}</span>
|
||
<span class="price_sellby__unit">${unit?.sellBy > 1 ? unit?.plural : unit?.singular}</span>
|
||
</span>
|
||
${convertedPrice && prod.unit?.unitConvertedFormat ? `<span class="price --convert">(${convertedPrice} / ${prod.unit?.unitConvertedFormat})</span>` : ""}
|
||
</strong>
|
||
${pointsPrice ? `<span class="price --points">${pointsPrice} pkt.</span>` : ""}
|
||
${price.value === 0 ? `<a class="price --phone" href="/contact.php" tabindex="-1" title="${idmHotspotTextObject["Kliknij, by przejść do formularza kontaktu"]}">${idmHotspotTextObject["Cena na telefon"]}</a>` : ""}
|
||
${prodExchangedData?.beforeRebate?.visible ? `<span class="price --before-rebate">${prodExchangedData?.beforeRebate?.html}</span>` : ""}
|
||
${prodExchangedData?.newPriceEffectiveUntil?.visible ? `<span class="price --new-price new_price">${prodExchangedData?.newPriceEffectiveUntil?.html}</span>` : ""}
|
||
${this.markupOmnibus(prodExchangedData?.omnibus)}
|
||
${prodExchangedData?.max?.visible ? `<span class="price --max">${prodExchangedData?.max?.html}</span>` : ""}
|
||
`;
|
||
}
|
||
|
||
/**
|
||
* Tworzy znacznik HTML dla omnibusa.
|
||
*
|
||
* @param {object} omnibus - dane omnibusa
|
||
* @param {string} omnibus.html - kod html zwykłego omnibusa
|
||
* @param {string} omnibus.percent - % przeceny omnibusa
|
||
* @param {string} omnibus.price - cena omnibusa
|
||
* @param {boolean} omnibus.visible - czy omnibus jest widoczny
|
||
* @returns {string} Zwraca HTML dla sekcji omnibusa lub pusty string, jeśli niewidoczny.
|
||
*/
|
||
|
||
markupOmnibus(omnibus){
|
||
if(!omnibus?.visible) return "";
|
||
|
||
if(!this.options.omnibusTooltip) return `<span class="price --omnibus omnibus_price">${omnibus?.html}</span>`;
|
||
else return `
|
||
<span class="price --omnibus omnibus_price">
|
||
<span class="omnibus_price__text">${idmHotspotTextObject["Najniższa cena"]}:</span>
|
||
<del class="omnibus_price__value">${omnibus.price}</del>
|
||
<span class="price_percent">${omnibus.percent}</span>
|
||
<span class="idm_tooltip">
|
||
<span class="idm_tooltip__info_icon"></span>
|
||
<p class="idm_tooltip__content --one-line">${idmHotspotTextObject["Najniższa cena z 30 dni przed obniżką"]}</p>
|
||
</span>
|
||
</span>
|
||
`;
|
||
}
|
||
|
||
markupAddToBasket(prod){
|
||
let markup = "";
|
||
if(!this.options.addToBasket) return markup;
|
||
|
||
// link do produktu jak nie jest to zwykły produkt
|
||
if(prod.type !== "product" || prod.sizes[0].amount === 0) markup = `<a class="btn --solid --medium add_to_basket__link" href="${prod.href}">Zobacz produkt</a>`;
|
||
else if(this.options.addToBasket === "range") // +-
|
||
markup = `<form class="add_to_basket --range" action="/basketchange.php" type="post">
|
||
<input name="mode" type="hidden" value="1">
|
||
<input name="product" type="hidden" value="${prod.id}">
|
||
${this.markupSize(prod)}
|
||
<div class="idm-products-banner__qty"
|
||
data-sell-by="${prod.unit?.sellBy}"
|
||
data-precision="${prod.unit?.precision}"
|
||
data-max="${prod.sizes[0].amount === -1 ? 999999 : prod.sizes[0].amount}"
|
||
>
|
||
<button type="button" class="idm-products-banner__qty-decrease" aria-label="${idmHotspotTextObject["Zmniejsz ilość"]}">−</button>
|
||
|
||
<input type="number"
|
||
name="number"
|
||
class="idm-products-banner__qty-input"
|
||
value="${prod.unit?.sellBy}"
|
||
step="${prod.unit?.sellBy}"
|
||
min="${prod.unit?.sellBy}"
|
||
max="${prod.sizes[0].amount === -1 ? 999999 : prod.sizes[0].amount}"
|
||
aria-label="${idmHotspotTextObject["Ilość"]}"
|
||
>
|
||
|
||
<button type="button" class="idm-products-banner__qty-increase" aria-label="${idmHotspotTextObject["Zwiększ ilość"]}">+</button>
|
||
</div>
|
||
|
||
<button type="submit" class="btn --solid --medium add_to_basket__button" tabindex="0" data-success="${idmHotspotTextObject["Dodany"]}" data-error="${idmHotspotTextObject["Wystąpił błąd"]}" data-text="${idmHotspotTextObject["Do koszyka"]}">
|
||
<span>${idmHotspotTextObject["Do koszyka"]}</span>
|
||
</button>
|
||
</form>`;
|
||
else // Zwykłe dodanie do koszyka
|
||
markup = `
|
||
<form class="add_to_basket" action="/basketchange.php" type="post">
|
||
<input name="mode" type="hidden" value="1">
|
||
<input name="product" type="hidden" value="${prod.id}">
|
||
${this.markupSize(prod)}
|
||
<input name="number" type="hidden" value="${prod.unit?.sellBy}">
|
||
<button type="submit" class="btn --solid --medium add_to_basket__button" tabindex="0" data-success="${idmHotspotTextObject["Dodany"]}" data-error="${idmHotspotTextObject["Wystąpił błąd"]}" data-text="${idmHotspotTextObject["Do koszyka"]}">
|
||
<span>${idmHotspotTextObject["Do koszyka"]}</span>
|
||
</button>
|
||
</form>`;
|
||
return markup;
|
||
}
|
||
|
||
markupSize(prod){
|
||
return `<input name="size" type="hidden" value="${prod.sizes[0].id}">`;
|
||
// if(!this.options?.selectSize || prod.sizes.length === 1) return `<input name="size" type="hidden" value="${prod.sizes[0].id}">`;
|
||
|
||
|
||
// const sizesName = `${this.id}-${prod.id}`;
|
||
// return `
|
||
// <div class="product__select_sizes">
|
||
// ${prod.sizes.reduce((acc, val)=>{
|
||
// const inputId = `${sizesName}-${val.id}`;
|
||
|
||
// return acc + `
|
||
// <label class="product__size_select" for="${sizesName}">
|
||
// <input type="radio" name="${sizesName}" id="${sizesName}"/>
|
||
// <span>${val.name}</span>
|
||
// </label>
|
||
// `
|
||
// }, "")}
|
||
// </div>
|
||
// `;
|
||
}
|
||
|
||
markupHotspotContainer(){
|
||
return `<section id="${this.id}" class="hotspot__wrapper idm__hotspot --init idm-loading ${this?.classes}">
|
||
<div class="hotspot --initialized">
|
||
${this?.title ? `
|
||
<h3 class="hotspot__name headline__wrapper">
|
||
<span class="headline"><span class="headline__name" aria-label="${this.title}">${this.title}</span></span>
|
||
</h3>
|
||
` : ""}
|
||
${this.markupHotspotSwiperContainer()}
|
||
</div>
|
||
</section>`;
|
||
}
|
||
markupHotspotSwiperContainer(productsHTML=""){
|
||
return `<div class="products__wrapper swiper">
|
||
<div class="products hotspot__products swiper-wrapper">
|
||
${productsHTML}
|
||
</div>
|
||
</div>
|
||
<div class="swiper-button-prev --rounded --edge idm-button-prev"><i class="icon-angle-left"></i></div>
|
||
<div class="swiper-button-next --rounded --edge idm-button-next"><i class="icon-angle-right"></i></div>
|
||
<div class="swiper-pagination"></div>`;
|
||
}
|
||
|
||
// ========================================================
|
||
// HANDLERY ZDARZEŃ
|
||
// ========================================================
|
||
|
||
/**
|
||
* Obsługuje dodanie produktu do koszyka (GraphQL).
|
||
*/
|
||
async handleAddToBasket(e){
|
||
const formEl = e.target.closest("form.add_to_basket");
|
||
if(!formEl) return;
|
||
try{
|
||
// pobieranie danych i elementów
|
||
formEl.classList.add("--loading")
|
||
const buttonEl = formEl.querySelector(".add_to_basket__button");
|
||
e.preventDefault();
|
||
|
||
const id = formEl.querySelector("input[name='product']")?.value;
|
||
const size = formEl.querySelector("input[type='hidden'][name='size']")?.value;
|
||
const number = formEl.querySelector("input[name='number']")?.value;
|
||
|
||
// dodanie do koszyka
|
||
const res = await fetch(`/graphql/v1/`, {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
},
|
||
body: IDM_HOTSPOT_ADD_TO_BASKET(id, size, number)
|
||
});
|
||
const data = await res.json();
|
||
|
||
// Błąd
|
||
if(data?.data?.addProductsToBasket?.status !== "success") throw new Error(data);
|
||
else{
|
||
localStorage.setItem('addedtoBasket', true);
|
||
// Obsługiwanie sukcesu
|
||
app_shop.graphql.trackingEvents(res);
|
||
buttonEl.classList.add("--success");
|
||
|
||
// Dodawanie do koszyka na stronie basketedit.php będzie wymagał innego indywidualnego kodu!!!!!
|
||
app_shop.fn?.menu_basket_cache?.();
|
||
|
||
// STRONA KOSZYKA
|
||
if(typeof app_shop.fn?.basket?.reloadForm === "function"){
|
||
const existingBasketBlockQuantity = document.querySelector(`.basket__block[data-product-id="${id}"][data-product-size="${size}"] input.buy__more_input.quantity__input[type="number"]`);
|
||
|
||
// Dodanie do ilości produktu jeśli już był dodany do koszyka
|
||
if(existingBasketBlockQuantity) existingBasketBlockQuantity.value = +existingBasketBlockQuantity.value + 1;
|
||
|
||
|
||
// Przeładowanie koszyka na stronie basketedit.html
|
||
app_shop.fn?.basket?.reloadForm();
|
||
}
|
||
|
||
buttonEl.innerHTML = `<span>${buttonEl.dataset.success}</span>`;
|
||
setTimeout(()=>{
|
||
buttonEl.innerHTML = `<span>${buttonEl.dataset.text}</span>`;
|
||
buttonEl.classList.remove("--success");
|
||
}, 3000);
|
||
}
|
||
}catch(err){
|
||
console.error(err);
|
||
Alertek.Error(idmHotspotTextObject["Coś poszło nie tak podczas dodawania do koszyka. Spróbuj ponownie lub odśwież stronę"]);
|
||
buttonEl.innerHTML = `<span>${buttonEl.dataset.error}</span>`;
|
||
buttonEl.classList.add("--error")
|
||
setTimeout(()=>{
|
||
buttonEl.classList.remove("--error")
|
||
buttonEl.innerHTML = `<span>${buttonEl.dataset.text}</span>`;
|
||
}, 3000);
|
||
}finally{
|
||
formEl.classList.remove("--loading")
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Obsługuje dodanie produktu do Porównania
|
||
*/
|
||
async handleAddToCompare(e){
|
||
const compareBtnEl = e.target.closest(".idm-products-banner__compare-btn");
|
||
const compareId = compareBtnEl?.dataset?.compareId;
|
||
if (!compareBtnEl || !compareId) return;
|
||
e.preventDefault();
|
||
try {
|
||
compareBtnEl.classList.add("--loading");
|
||
const compareUrl = `/${app_shop?.vars?.language?.symbol || "pl"}/settings.html?comparers=add&product=${compareId}`;
|
||
|
||
const res = await fetch(compareUrl);
|
||
console.log(res);
|
||
if (!res.ok) throw new Error(`${idmHotspotTextObject["Wystąpił błąd"]}`);
|
||
|
||
compareBtnEl.classList.add("--success");
|
||
|
||
const compareContainerQuery = "#menu_compare_product";
|
||
if (document.querySelector(compareContainerQuery)) {
|
||
app_shop.fn?.load(
|
||
window.location.pathname,
|
||
[[compareContainerQuery, compareContainerQuery]],
|
||
function () {},
|
||
"?set_render=content"
|
||
);
|
||
}
|
||
|
||
setTimeout(() => {
|
||
compareBtnEl.classList.remove("--success");
|
||
}, 2000);
|
||
} catch (err) {
|
||
console.error(err);
|
||
Alertek.Error(`${idmHotspotTextObject["Coś poszło nie tak. Spróbuj ponownie później"]}`);
|
||
} finally {
|
||
compareBtnEl.classList.remove("--loading");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Obsługuje dodanie produktu do Listy zakupowej
|
||
*/
|
||
handleAddToFav(e){
|
||
const favEl = e.target.closest(".product__favorite");
|
||
|
||
if(!favEl) return;
|
||
|
||
this.addToFavFn([[favEl.dataset.productId, favEl.dataset.productSize]]);
|
||
}
|
||
|
||
/**
|
||
* Obsługuje kliknięcia w przyciski +/− przy wyborze ilości.
|
||
*/
|
||
handleQuantityButtonClick(e){
|
||
if(e.target.classList.contains("idm-products-banner__qty-input")) return e.target.select();
|
||
const wrapper = e.target.closest(".idm-products-banner__qty");
|
||
|
||
const input = wrapper.querySelector(".idm-products-banner__qty-input");
|
||
const step = parseFloat(wrapper.dataset.sellBy || "1");
|
||
const precision = parseInt(wrapper.dataset.precision || "0");
|
||
const max = parseFloat(wrapper.dataset.max || "999999");
|
||
let current = parseFloat(input.value) || 0;
|
||
|
||
if (e.target.classList.contains("idm-products-banner__qty-increase")) {
|
||
current += step;
|
||
if (current > max){
|
||
current = max;
|
||
this.rangeMaxAlert(max)
|
||
}
|
||
} else if (e.target.classList.contains("idm-products-banner__qty-decrease")) {
|
||
current -= step;
|
||
if (current < step){
|
||
current = step;
|
||
this.rangeMinAlert(step)
|
||
}
|
||
}
|
||
input.value = current.toFixed(precision);
|
||
}
|
||
|
||
/**
|
||
* Obsługuje Pokazywanie zdjęcia na hover
|
||
*/
|
||
handleShowSecondImage(e){
|
||
const prodIconEl = e.target.closest(".product__icon");
|
||
if(!prodIconEl) return;
|
||
|
||
prodIconEl.classList.add("--toggle-icon");
|
||
}
|
||
|
||
handleHideSecondImage(e){
|
||
const prodIconEl = e.target.closest(".product__icon");
|
||
if(!prodIconEl) return;
|
||
|
||
prodIconEl.classList.remove("--toggle-icon");
|
||
}
|
||
/**
|
||
* Walidacja zmian ilości w polu input.
|
||
*/
|
||
|
||
handleQuantityInputChange(e){
|
||
if(e.target.value > +e.target.max){
|
||
this.rangeMaxAlert(e.target.max)
|
||
e.target.value = +e.target.max
|
||
}else if(e.target.value < +e.target.min){
|
||
this.rangeMinAlert(e.target.min)
|
||
e.target.value = +e.target.min;
|
||
}
|
||
}
|
||
|
||
|
||
handleSelectVersion(e){
|
||
if(e.target.closest(".product__version_more")) return;
|
||
e.preventDefault();
|
||
const closestVersion = e.target.closest(".product__version_single:not(.--active)");
|
||
const prodEl = e.target.closest(".product");
|
||
|
||
if(!closestVersion || !prodEl) return;
|
||
|
||
this.reloadProduct(prodEl, closestVersion.dataset.productId);
|
||
}
|
||
|
||
/**
|
||
* Lazy-load hotspotu – wczytuje dane dopiero, gdy element pojawi się w viewportcie.
|
||
*/
|
||
handleObserveHotspotOnce() {
|
||
if (!this.hotspotEl) return;
|
||
|
||
const observer = new IntersectionObserver((entries, obs) => {
|
||
entries.forEach(entry => {
|
||
if (entry.isIntersecting) {
|
||
this.fillHotspot(); // run your callback
|
||
obs.disconnect(); // stop observing after first trigger
|
||
}
|
||
});
|
||
}, { root: null, rootMargin: "0px", threshold: 0.1 });
|
||
|
||
observer.observe(this.hotspotEl);
|
||
}
|
||
|
||
// ========================================================
|
||
// FUNKCJE POMOCNICZE
|
||
// ========================================================
|
||
|
||
/**
|
||
* Wyświetla alert o maksymalnej ilości produktu.
|
||
*/
|
||
rangeMaxAlert(max){
|
||
Alertek.Error(`${idmHotspotTextObject["Maksymalna liczba sztuk tego towaru które możesz dodać do koszyka to:"]} ${max}`)
|
||
}
|
||
/**
|
||
* Wyświetla alert o minimalnej ilości produktu.
|
||
*/
|
||
rangeMinAlert(min){
|
||
Alertek.Error(`${idmHotspotTextObject["Minimalna liczba sztuk tego towaru które możesz dodać do koszyka to:"]} ${min}`)
|
||
}
|
||
|
||
/**
|
||
* Ustawia jednakową wysokość elementów (np. nazw lub cen).
|
||
*/
|
||
|
||
setHeight(options){
|
||
const { selector, selectors, container } = options || {}
|
||
if ((!selector && !selectors) || !container) return
|
||
|
||
const containerElement = document.querySelector(container)
|
||
if (!containerElement) return
|
||
|
||
const adjustAllHeights = itemSelector => {
|
||
const targets = containerElement.querySelectorAll(itemSelector)
|
||
if (!targets.length) return
|
||
|
||
targets.forEach(el => (el.style.minHeight = ''))
|
||
|
||
const max = Math.max(...[...targets].map(el => el.offsetHeight || 0))
|
||
|
||
targets.forEach(el => (el.style.minHeight = `${max}px`))
|
||
}
|
||
|
||
if (selector) adjustAllHeights(selector)
|
||
if (selectors?.length) selectors.forEach(adjustAllHeights)
|
||
}
|
||
|
||
/**
|
||
* Przeładowanie pojedynczego produktu
|
||
*/
|
||
async reloadProduct(prodEl, newProdId){
|
||
try{
|
||
prodEl.classList.add("--loading");
|
||
const res = await fetch(`/graphql/v1/`, {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
},
|
||
body: IDM_PRODUCT_GQL(`productId: ${newProdId}`),
|
||
});
|
||
const data = await res.json();
|
||
|
||
const productData = data?.data?.product?.product;
|
||
if(!productData) throw new Error("Nie udało się pobrać danych o produkcie");
|
||
|
||
|
||
const prodHTML = this.markupProductInnerHTML(productData);
|
||
|
||
prodEl.dataset.id = newProdId;
|
||
prodEl.innerHTML = prodHTML;
|
||
if(productData.price.price[this.priceType].value === 0) prodEl.classList.add("--phone");
|
||
else prodEl.classList.remove("--phone");
|
||
|
||
this.initSingleEvent(prodEl);
|
||
this.setHeight({
|
||
selectors: [
|
||
`#${this.id} .product__prices`,
|
||
`#${this.id} .product__name`,
|
||
],
|
||
container: `#${this.id} .products__wrapper`,
|
||
});
|
||
}catch(err){
|
||
Alertek?.Error(idmHotspotTextObject["Błąd przy pobieraniu danych"]);
|
||
console.error(err);
|
||
}finally{
|
||
prodEl.classList.remove("--loading");
|
||
}
|
||
}
|
||
// ========================================================
|
||
// INICJALIZACJA
|
||
// ========================================================
|
||
|
||
/**
|
||
* Wykonywana po pełnej inicjalizacji hotspotu (Swiper, eventy, wysokości).
|
||
*/
|
||
async afterInit(){
|
||
try{
|
||
if(!this.hotspotEl) throw new Error("Nie znaleziono elementu");
|
||
|
||
if(this.title && !this.hotspotEl.querySelector(".hotspot__name.headline__wrapper")){
|
||
this.hotspotEl.querySelector(".hotspot.--initialized")?.insertAdjacentHTML("afterbegin", `<h3 class="hotspot__name headline__wrapper">
|
||
<span class="headline"><span class="headline__name" aria-label="${this.title}">${this.title}</span></span>
|
||
</h3>`);
|
||
}
|
||
|
||
await this.initSwiper();
|
||
// IDM setHeight
|
||
this.setHeight({
|
||
selectors: [
|
||
`#${this.id} .product__prices`,
|
||
`#${this.id} .product__name`,
|
||
],
|
||
container: `#${this.id} .products__wrapper`,
|
||
});
|
||
this.initEvents();
|
||
console.log(`Initialized hotspot #${this.id}`);
|
||
|
||
if(typeof this.options?.callbackFn === "function") this.options?.callbackFn();
|
||
}catch(err){
|
||
console.error(idmHotspotTextObject["Wystąpił błąd z inicjalizacją. Proszę odśwież stronę"], err);
|
||
}
|
||
}
|
||
|
||
initExternalFunctions(){
|
||
this.addToFavFn = app_shop.fn?.shoppingList?.addProductToList;
|
||
}
|
||
|
||
/**
|
||
* Pobiera dane, wypełnia markup i inicjuje Swipera.
|
||
*/
|
||
async fillHotspot(){
|
||
// Zdefiniowanie funkcji do dodawania do ulubionych
|
||
this.initExternalFunctions();
|
||
try{
|
||
if(!this.products){
|
||
if(!this?.query?.graphFn || !this?.query?.string) this.setQueryData();
|
||
|
||
// pobranie danych o produktach
|
||
await this.getHotspotData();
|
||
if(!this.products) throw new Error(idmHotspotTextObject["Nie znaleziono produktów"]);
|
||
}
|
||
|
||
|
||
// Wstawienie markupa na strone
|
||
if(this.hotspotEl.querySelector(".products.hotspot__products")) this.hotspotEl.querySelector(".products.hotspot__products").insertAdjacentHTML("beforeend", this.markup());
|
||
else if(this.hotspotEl.querySelector(".hotspot")){
|
||
this.hotspotEl.querySelector(".hotspot")?.insertAdjacentHTML("beforeend", this.markupHotspotSwiperContainer(this.markup()));
|
||
}
|
||
else{
|
||
throw new Error("Nie udało się wstawić produktów! Zła struktura HTML")
|
||
}
|
||
|
||
|
||
this.hotspotEl.classList.remove("idm-loading");
|
||
|
||
// init swiper + add to basket
|
||
this.afterInit();
|
||
}catch(err){
|
||
console.error(idmHotspotTextObject["Wystąpił błąd"], err);
|
||
this.hotspotEl.remove();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Inicjuje instancję Swipera dla hotspotu.
|
||
*/
|
||
async initSwiper(){
|
||
try{
|
||
// swiper || slick
|
||
if(this.options?.swiper){
|
||
// Opcje swipera
|
||
if(typeof this?.options?.swiper === "boolean") this.options.swiper = IdmHotspot.idmDefaultSwiperConfig;
|
||
|
||
// Wywołanie swipera
|
||
const selectedSwiper = new HotspotSlider({
|
||
selector: `#${this.id} .swiper`,
|
||
hotspotName: `${this.id}`,
|
||
options: this.options.swiper,
|
||
});
|
||
await selectedSwiper.init();
|
||
|
||
if(this.options.swiperScrollbar) new IdmSwiperProgress(selectedSwiper, `#${this.id} .swiper`);
|
||
}
|
||
}catch(err){
|
||
console.error(idmHotspotTextObject["Wystąpił błąd z inicjalizacją. Proszę odśwież stronę"], err);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Inicjuje eventy dla produktów w hotspotcie.
|
||
*/
|
||
initEvents(){
|
||
this.hotspotEl.querySelectorAll(".product").forEach(prodEl=>{
|
||
this.initSingleEvent(prodEl);
|
||
})
|
||
}
|
||
initSingleEvent(prodEl){
|
||
// DODAWANIE DO KOSZYKA
|
||
if(this.options?.addToBasket){
|
||
const addToBasketEl = prodEl.querySelector("form.add_to_basket");
|
||
|
||
addToBasketEl?.addEventListener("submit", this.handleAddToBasket);
|
||
// + -
|
||
if(this?.options?.addToBasket === "range"){
|
||
addToBasketEl?.querySelector(".idm-products-banner__qty")?.addEventListener("click",this.handleQuantityButtonClick);
|
||
addToBasketEl?.querySelector(".idm-products-banner__qty-input")?.addEventListener("input",this.handleQuantityInputChange);
|
||
}
|
||
}
|
||
// Dodaj do ulubionych
|
||
if(this.options?.addToFavorites && typeof this.addToFavFn === "function") prodEl.querySelector(".product__favorite")?.addEventListener("click", this.handleAddToFav);
|
||
|
||
// Porównanie
|
||
if(this.options?.addToCompare) prodEl.querySelector(".idm-products-banner__compare-btn")?.addEventListener("click", this.handleAddToCompare);
|
||
|
||
// Hover drugie zdjęcie
|
||
if(this.options?.showSecondImage){
|
||
const prodIconEl = prodEl.querySelector(".product__icon");
|
||
if(prodIconEl.querySelector(".product__image.--second")){
|
||
prodIconEl?.addEventListener("mouseover", this.handleShowSecondImage);
|
||
prodIconEl?.addEventListener("mouseleave", this.handleHideSecondImage);
|
||
}
|
||
}
|
||
|
||
// Wybór wersji
|
||
if(this.options?.selectVersion) prodEl.querySelector(".product__versions")?.addEventListener("click", this.handleSelectVersion);
|
||
}
|
||
|
||
/**
|
||
* Inicjuje kontener hotspotu w określonym miejscu DOM.
|
||
*/
|
||
initHotspotContainer(){
|
||
const selectedEl = document.querySelector(this?.placement.selector);
|
||
if(!selectedEl) throw new Error(idmHotspotTextObject["Nie znaleziono kontenera"]);
|
||
const markup = this.markupHotspotContainer();
|
||
selectedEl.insertAdjacentHTML(this.placement.insert || "afterend", markup);
|
||
|
||
this.hotspotEl = document.getElementById(this.id);
|
||
}
|
||
|
||
/**
|
||
* Główna metoda inicjalizująca hotspot (lazy lub natychmiast).
|
||
*/
|
||
init(){
|
||
const queryString = window.location.search;
|
||
const urlParams = new URLSearchParams(queryString);
|
||
const dev = urlParams.get('dev')
|
||
|
||
if(this.options?.devMode && dev !== "true") return console.error(`Brak włączonego devMode. Ramka ${this.id} nie mogła zostać utworzona!`);
|
||
|
||
if(!this.hotspotEl || !document.contains(this.hotspotEl)) this.initHotspotContainer();
|
||
|
||
if(this.options?.lazy) this.handleObserveHotspotOnce();
|
||
else this.fillHotspot();
|
||
}
|
||
}
|
||
|
||
/*
|
||
==============================================================
|
||
SWIPER PASEK
|
||
==============================================================
|
||
*/
|
||
|
||
class IdmSwiperProgress {
|
||
constructor(swiper, selector) {
|
||
this.swiper = swiper?.slider?.slider ?? swiper?.slider ?? swiper;
|
||
this.selector = selector;
|
||
this.scrollbarEl = null;
|
||
this.progressEl = null;
|
||
this.isDragging = false;
|
||
|
||
this.init();
|
||
}
|
||
|
||
init() {
|
||
const el = document.querySelector(this.selector);
|
||
if (!el || el.querySelector(".idm-scrollbar")) return;
|
||
|
||
el.insertAdjacentHTML(
|
||
"beforeend",
|
||
`<div class="idm-scrollbar swiper-scrollbar"><div class="idm-progress"></div></div>`
|
||
);
|
||
|
||
this.scrollbarEl = el.querySelector(".idm-scrollbar");
|
||
this.progressEl = this.scrollbarEl.querySelector(".idm-progress");
|
||
|
||
this.updateBarWidth();
|
||
this.addDragTracking();
|
||
|
||
this.swiper.on("progress", () => this.updateProgress());
|
||
this.swiper.on("breakpoint", () => {this.updateBarWidth()});
|
||
}
|
||
|
||
updateBarWidth() {
|
||
const { slidesPerGroup, slidesPerView } = this.swiper.params;
|
||
const totalSlides = this.swiper.slides.length;
|
||
|
||
const progressWidth = 100 / (((totalSlides - slidesPerView) / slidesPerGroup) + 1);
|
||
this.progressEl.style.width = `${progressWidth}%`;
|
||
|
||
if (progressWidth >= 100 || progressWidth <= 0) this.scrollbarEl.style.display = "none";
|
||
else this.scrollbarEl.style.display = "";
|
||
}
|
||
|
||
updateProgress() {
|
||
const progress = this.swiper.progress;
|
||
const { slidesPerGroup, slidesPerView } = this.swiper.params;
|
||
const totalSlides = this.swiper.slides.length;
|
||
|
||
const progressWidth = 100 / (((totalSlides - slidesPerView) / slidesPerGroup) + 1);
|
||
const newLeft = (100 - progressWidth) * progress;
|
||
this.progressEl.style.left = `${Math.min(
|
||
100 - progressWidth,
|
||
Math.max(0, newLeft)
|
||
)}%`;
|
||
}
|
||
|
||
addDragTracking() {
|
||
const handle = this.progressEl;
|
||
let grabOffset = 0;
|
||
let scrollbarWidth = 0;
|
||
let handleWidth = 0;
|
||
|
||
const startDrag = (e) => {
|
||
this.isDragging = true;
|
||
this.scrollbarEl.classList.add("--drag-start");
|
||
|
||
const rect = this.scrollbarEl.getBoundingClientRect();
|
||
const handleRect = handle.getBoundingClientRect();
|
||
scrollbarWidth = rect.width;
|
||
handleWidth = handleRect.width;
|
||
|
||
const clientX = e.touches ? e.touches[0].clientX : e.clientX;
|
||
grabOffset = clientX - handleRect.left;
|
||
|
||
document.addEventListener("mousemove", handleDrag);
|
||
document.addEventListener("mouseup", stopDrag);
|
||
document.addEventListener("touchmove", handleDrag);
|
||
document.addEventListener("touchend", stopDrag);
|
||
};
|
||
|
||
const handleDrag = (e) => {
|
||
if (!this.isDragging) return;
|
||
const clientX = e.touches ? e.touches[0].clientX : e.clientX;
|
||
const rect = this.scrollbarEl.getBoundingClientRect();
|
||
let newLeftPx = clientX - rect.left - grabOffset;
|
||
const maxLeft = scrollbarWidth - handleWidth;
|
||
newLeftPx = Math.max(0, Math.min(maxLeft, newLeftPx));
|
||
const progress = newLeftPx / maxLeft;
|
||
this.swiper.setProgress(progress, 0);
|
||
};
|
||
|
||
const stopDrag = () => {
|
||
if (!this.isDragging) return;
|
||
this.isDragging = false;
|
||
document.removeEventListener("mousemove", handleDrag);
|
||
document.removeEventListener("mouseup", stopDrag);
|
||
document.removeEventListener("touchmove", handleDrag);
|
||
document.removeEventListener("touchend", stopDrag);
|
||
this.scrollbarEl.classList.remove("--drag-start");
|
||
this.swiper.slideReset(400);
|
||
};
|
||
|
||
handle.addEventListener("mousedown", startDrag);
|
||
handle.addEventListener("touchstart", startDrag);
|
||
}
|
||
}
|
||
// ========================================================
|
||
// TOOLTIP
|
||
// ========================================================
|
||
function idmShowTooltip(tooltipEl){
|
||
const tooltipContentEl = tooltipEl.querySelector(".idm_tooltip__content");
|
||
if(!tooltipContentEl) return;
|
||
|
||
tooltipContentEl.classList.add("--visible");
|
||
|
||
// Logika pokazywania się i chowania tooltipa
|
||
let timeoutVar;
|
||
|
||
function onMouseLeave() {
|
||
timeoutVar = idmHideTooltipTimer(tooltipEl);
|
||
}
|
||
|
||
function onMouseEnter() {
|
||
clearTimeout(timeoutVar);
|
||
}
|
||
|
||
function onScroll() {
|
||
idmHideTooltip(tooltipEl);
|
||
}
|
||
|
||
// Store references for later removal
|
||
tooltipEl._onMouseLeave = onMouseLeave;
|
||
tooltipEl._onMouseEnter = onMouseEnter;
|
||
tooltipEl._onScroll = onScroll;
|
||
|
||
tooltipEl.addEventListener("mouseleave", onMouseLeave);
|
||
tooltipEl.addEventListener("mouseenter", onMouseEnter);
|
||
document.addEventListener("scroll", onScroll);
|
||
}
|
||
|
||
function idmHideTooltipTimer(tooltipEl){
|
||
return setTimeout(() => idmHideTooltip(tooltipEl), 1500);
|
||
}
|
||
function idmHideTooltip(tooltipEl){
|
||
const tooltipContentEl = tooltipEl.querySelector(".idm_tooltip__content");
|
||
if (!tooltipContentEl) return;
|
||
|
||
tooltipContentEl.classList.remove("--visible");
|
||
|
||
tooltipEl.removeEventListener("mouseleave", tooltipEl._onMouseLeave);
|
||
tooltipEl.removeEventListener("mouseenter", tooltipEl._onMouseEnter);
|
||
document.removeEventListener("scroll", tooltipEl._onScroll);
|
||
|
||
delete tooltipEl._onMouseLeave;
|
||
delete tooltipEl._onMouseEnter;
|
||
delete tooltipEl._onScroll;
|
||
}
|
||
|
||
|
||
document.addEventListener("DOMContentLoaded", ()=>{
|
||
document.body.addEventListener("click", e=>{
|
||
const tooltipEl = e.target.closest(".idm_tooltip");
|
||
if(!e.target.closest(".idm_tooltip__info_icon") || !tooltipEl) return;
|
||
|
||
e.preventDefault();
|
||
idmShowTooltip(tooltipEl);
|
||
});
|
||
});
|
||
// new IdmHotspot({
|
||
// id: "idmTestHotspot1",
|
||
// title: "tescik",
|
||
// products: [] // Tablica produktów
|
||
// placement: {
|
||
// selector: "#content",
|
||
// insert: "beforeend",
|
||
// },
|
||
// source: {
|
||
// productsMenu: 1649,
|
||
// producersId: [],
|
||
// seriesId: [],
|
||
// parametersId: [],
|
||
// priceRange: {
|
||
// from: 0,
|
||
// to: 150,
|
||
// }
|
||
// }
|
||
// options: {
|
||
// lazy: true,
|
||
// addToBasket: "range",
|
||
// swiper: true,
|
||
// }
|
||
// });
|
||
// {
|
||
// id: "idmMainHotspot2",
|
||
// title: "Super ramka rekomendacji",
|
||
// placement: {
|
||
// selector: "#content",
|
||
// insert: "beforeend"
|
||
// },
|
||
// source: {
|
||
// productsMenu: 488,
|
||
// },
|
||
// options: {
|
||
// lazy: true,
|
||
// addToBasket: "range",
|
||
// swiper: true,
|
||
// }
|
||
// }
|
||
|
||
|
||
async function idmPrepareHotspotObject(selectedContainerEl){
|
||
selectedContainerEl.classList.add("--init");
|
||
const source = {};
|
||
|
||
|
||
if(selectedContainerEl.dataset?.hotspotsType) source.hotspotsType = selectedContainerEl.dataset.hotspotsType;
|
||
else {
|
||
if(selectedContainerEl.dataset?.productsId) source.productsId = selectedContainerEl.dataset.productsId.split(",");
|
||
if(selectedContainerEl.dataset?.productsMenu) source.productsMenu = selectedContainerEl.dataset.productsMenu;
|
||
if(selectedContainerEl.dataset?.producersId) source.producersId = selectedContainerEl.dataset.producersId;
|
||
if(selectedContainerEl.dataset?.seriesId) source.seriesId = selectedContainerEl.dataset.seriesId;
|
||
if(selectedContainerEl.dataset?.parametersId) source.seriesId = selectedContainerEl.dataset.parametersId;
|
||
if(selectedContainerEl.dataset?.priceFrom && selectedContainerEl.dataset?.priceTo) source.priceRange = {from: +selectedContainerEl.dataset.priceFrom, to: +selectedContainerEl.dataset.priceTo};
|
||
}
|
||
|
||
|
||
if(Object.keys(source).length === 0){
|
||
console.error();
|
||
selectedContainerEl?.remove();
|
||
return;
|
||
}
|
||
|
||
const idmHotspotObj = {
|
||
id: selectedContainerEl?.id,
|
||
source,
|
||
hotspotEl: selectedContainerEl
|
||
};
|
||
|
||
if(selectedContainerEl?.dataset?.lazy) idmHotspotObj.options = {lazy: selectedContainerEl?.dataset?.lazy === "true" ? true : false};
|
||
|
||
new IdmHotspot(idmHotspotObj)
|
||
}
|
||
|
||
|
||
document.querySelectorAll(".hotspot__wrapper.idm__hotspot").forEach(currentHotspot=>{
|
||
idmPrepareHotspotObject(currentHotspot)
|
||
})
|