1288 lines
47 KiB
JavaScript
1288 lines
47 KiB
JavaScript
///////////////////////////////////////////////////////////
|
||
// TEXT
|
||
// LITERAŁY
|
||
const idmHotspotTextObject = {
|
||
[<iai:variable vid="Kod rabatowy"/>]: <iai:variable vid="Kod rabatowy"/>,
|
||
[<iai:variable vid="Okazja"/>]: <iai:variable vid="Okazja"/>,
|
||
[<iai:variable vid="Promocja"/>]: <iai:variable vid="Promocja"/>,
|
||
[<iai:variable vid="Bestseller"/>]: <iai:variable vid="Bestseller"/>,
|
||
[<iai:variable vid="Nowość"/>]: <iai:variable vid="Nowość"/>,
|
||
[<iai:variable vid="Ilość"/>]: <iai:variable vid="Ilość"/>,
|
||
[<iai:variable vid="Najniższa cena"/>]: <iai:variable vid="Najniższa cena"/>,
|
||
[<iai:variable vid="Najniższa cena z 30 dni przed obniżką"/>]: <iai:variable vid="Najniższa cena z 30 dni przed obniżką"/>,
|
||
[<iai:variable vid="Zwiększ ilość"/>]: <iai:variable vid="Zwiększ ilość"/>,
|
||
[<iai:variable vid="Zmniejsz ilość"/>]: <iai:variable vid="Zmniejsz ilość"/>,
|
||
[<iai:variable vid="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"/>,
|
||
[<iai:variable vid="Cena regularna"/>]: <iai:variable vid="Cena regularna"/>,
|
||
[<iai:variable vid="Cena bez kodu"/>]: <iai:variable vid="Cena bez kodu"/>,
|
||
[<iai:variable vid="Cena nadchodząca od"/>]: <iai:variable vid="Cena nadchodząca od"/>,
|
||
[<iai:variable vid="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ę"/>,
|
||
[<iai:variable vid="Nie znaleziono produktów"/>]: <iai:variable vid="Nie znaleziono produktów"/>,
|
||
[<iai:variable vid="Błąd przy pobieraniu danych"/>]: <iai:variable vid="Błąd przy pobieraniu danych"/>,
|
||
[<iai:variable vid="Kliknij, by przejść do formularza kontaktu"/>]: <iai:variable vid="Kliknij, by przejść do formularza kontaktu"/>,
|
||
[<iai:variable vid="Cena na telefon"/>]: <iai:variable vid="Cena na telefon"/>,
|
||
[<iai:variable vid="Dodany"/>]: <iai:variable vid="Dodany"/>,
|
||
[<iai:variable vid="Wystąpił błąd"/>]: <iai:variable vid="Wystąpił błąd"/>,
|
||
[<iai:variable vid="Do koszyka"/>]: <iai:variable vid="Do koszyka"/>,
|
||
[<iai:variable vid="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:"/>,
|
||
[<iai:variable vid="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:"/>,
|
||
[<iai:variable vid="Wystąpił błąd z inicjalizacją. Proszę odśwież stronę"/>]: <iai:variable vid="Wystąpił błąd z inicjalizacją. Proszę odśwież stronę"/>,
|
||
[<iai:variable vid="Nie znaleziono kontenera"/>]: <iai:variable vid="Nie znaleziono kontenera"/>,
|
||
[<iai:variable vid="Nie znaleziono metody graphql"/>]: <iai:variable vid="Nie znaleziono metody graphql"/>,
|
||
};
|
||
// STRING
|
||
// const idmHotspotTextObject = {
|
||
// ["Kod rabatowy"]: "Kod rabatowy",
|
||
// ["Okazja"]: "Okazja",
|
||
// ["Promocja"]: "Promocja",
|
||
// ["Bestseller"]: "Bestseller",
|
||
// ["Nowość"]: "Nowość",
|
||
// ["Ilość"]: "Ilość",
|
||
// ["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",
|
||
// }
|
||
|
||
///////////////////////////////////////////////
|
||
// 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
|
||
zones
|
||
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
|
||
}
|
||
}
|
||
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 {\n addProductsToBasket(ProductInput: {id: ${t}, size: "${e}", quantity: ${a}}) {\n status\n results {\n status\n error {\n code\n message\n }\n productCode\n productId\n sizeId\n quantity\n quantityAvailable\n }\n clientDetailsInBasket {\n id\n login\n firstname\n lastname\n participationPartnerProgram\n usesVat\n email\n isWholesaler\n isWholesalerOrder\n clientIdUpc\n }\n }\n }`
|
||
});
|
||
/////////////////////////////////////////
|
||
// 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,
|
||
addToBasket: true, // true, false, "range"
|
||
addToFavorites: false,
|
||
swiper: true,
|
||
callbackFn: ()=>{},
|
||
swiperScrollbar: false,
|
||
omnibusTooltip: 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.init();
|
||
}
|
||
|
||
|
||
|
||
|
||
// ========================================================
|
||
// ASYNC – POBIERANIE DANYCH Z GRAPHQL
|
||
// ========================================================
|
||
/**
|
||
* Przygotowuje funkcję i zapytanie GraphQL w zależności od typu danych.
|
||
*/
|
||
getQueryData({productsID, productsMenu, hotspotsType}){
|
||
let graphFn, query;
|
||
if(productsID){
|
||
graphFn = IDM_PRODUCTS_GQL;
|
||
query = `searchInput: {productsId: [${productsID}]}`;
|
||
}else if(productsMenu){
|
||
graphFn = IDM_PRODUCTS_GQL;
|
||
query = `searchInput: {navigation: ${productsMenu}}`;
|
||
}else if(hotspotsType){
|
||
graphFn = IDM_HOTSPOTS_GQL;
|
||
query = `searchInput: {hotspot: ${hotspotsType}, limit: 16}`;
|
||
}
|
||
|
||
return [graphFn, query];
|
||
}
|
||
/**
|
||
* Ustawia dane zapytania GraphQL wewnątrz instancji.
|
||
*/
|
||
setQueryData(queryObj){
|
||
const [graphFn, queryString] = this.getQueryData(queryObj);
|
||
this.query.graphFn = graphFn;
|
||
this.query.string = queryString;
|
||
}
|
||
|
||
/**
|
||
* Pobiera dane hotspotu z API GraphQL.
|
||
*/
|
||
async getHotspotData(){
|
||
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){
|
||
// IDM DO POPRAWKI
|
||
const prodExchangedData = app_shop?.fn?.getOmnibusDetails?.({productData: prod}) || app_shop.fn?.idmGetOmnibusDetails({productData: 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}">
|
||
<div class="product__yousave --hidden">
|
||
<span class="product__yousave --label"></span>
|
||
<span class="product__yousave --value"></span>
|
||
</div>
|
||
<div class="product__additional">
|
||
${this.markupFavorite(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">
|
||
<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>
|
||
</div>`;
|
||
|
||
return singleMarkup;
|
||
}
|
||
|
||
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'}">
|
||
</span>`;
|
||
}
|
||
|
||
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}">
|
||
</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}">
|
||
</picture>`;
|
||
else markup += `<img src="${prod.icon}" loading="lazy" alt="${prod.name}">`
|
||
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;
|
||
}
|
||
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 ? `<span class="price --convert">${convertedPrice}</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[<iai:variable vid="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[<iai:variable vid="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}">
|
||
<input name="size" type="hidden" value="${prod.sizes[0].id}">
|
||
<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}">
|
||
<input name="size" type="hidden" value="${prod.sizes[0].id}">
|
||
<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;
|
||
}
|
||
|
||
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>
|
||
` : ""}
|
||
<div class="products__wrapper swiper">
|
||
<div class="products hotspot__products swiper-wrapper">
|
||
|
||
</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>
|
||
</div>
|
||
</section>`;
|
||
}
|
||
|
||
// ========================================================
|
||
// 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!!!!!
|
||
buttonEl.innerHTML = `<span>${buttonEl.dataset.success}</span>`;
|
||
setTimeout(()=>{
|
||
buttonEl.innerHTML = `<span>${buttonEl.dataset.text}</span>`;
|
||
app_shop.fn?.menu_basket_cache?.();
|
||
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 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);
|
||
}
|
||
|
||
/**
|
||
* 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;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 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);
|
||
}
|
||
|
||
|
||
handleAddToFav(){
|
||
|
||
}
|
||
|
||
// ========================================================
|
||
// 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)
|
||
}
|
||
// ========================================================
|
||
// 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({
|
||
productsID: this?.source?.productsId,
|
||
productsMenu: this?.source?.productsMenu,
|
||
hotspotsType: this?.source?.hotspotsType
|
||
});
|
||
|
||
// pobranie danych o produktach
|
||
await this.getHotspotData();
|
||
if(!this.products) throw new Error(idmHotspotTextObject["Nie znaleziono produktów"]);
|
||
}
|
||
|
||
|
||
// Wstawienie markupa na strone
|
||
this.hotspotEl.querySelector(".products.hotspot__products")?.insertAdjacentHTML("beforeend", this.markup());
|
||
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");
|
||
if(!addToBasketEl) return;
|
||
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);
|
||
}
|
||
}
|
||
// Tooltip
|
||
if(this.options?.omnibusTooltip){
|
||
const tooltipEl = prodEl.querySelector(".idm_tooltip");
|
||
|
||
tooltipEl.addEventListener("click", ()=>{
|
||
this.showTooltip(tooltipEl);
|
||
})
|
||
}
|
||
// Add to Favorites
|
||
if(this.options?.addToFavorites && typeof this.addToFavFn === "function"){
|
||
const favEl = prodEl.querySelector(".product__favorite")
|
||
if(favEl) favEl.addEventListener("click", ()=>{
|
||
this.addToFavFn([[favEl.dataset.productId, favEl.dataset.productSize]]);
|
||
})
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 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).
|
||
*/
|
||
async init(){
|
||
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",
|
||
// placement: {
|
||
// selector: "#content",
|
||
// insert: "beforeend",
|
||
// },
|
||
// source: {
|
||
// productsMenu: 1649
|
||
// }
|
||
// });
|
||
// {
|
||
// 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?.productsId) source.productsId = selectedContainerEl?.dataset?.productsId.split(",");
|
||
else if(selectedContainerEl?.dataset?.hotspotsType) source.hotspotsType = selectedContainerEl?.dataset?.hotspotsType;
|
||
else if(selectedContainerEl?.dataset?.productsMenu) source.productsMenu = selectedContainerEl?.dataset?.productsMenu;
|
||
else{
|
||
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)
|
||
})
|