///////////////////////////////////////////////////////////
// TEXT
// LITERAŁY
const idmHotspotTextObject = {
["Kod rabatowy"]: ,
["Okazja"]: ,
["Promocja"]: ,
["Bestseller"]: ,
["Nowość"]: ,
["Ilość"]: ,
["Porównaj"]: ,
["Dodaj do ulubionych"]: ,
["Najniższa cena"]: ,
["Najniższa cena z 30 dni przed obniżką"]: ,
["Zwiększ ilość"]: ,
["Zmniejsz ilość"]: ,
["Najniższa cena produktu w okresie 30 dni przed wprowadzeniem obniżki"]: ,
["Cena regularna"]: ,
["Cena bez kodu"]: ,
["Cena nadchodząca od"]: ,
["Coś poszło nie tak podczas dodawania do koszyka. Spróbuj ponownie lub odśwież stronę"]: ,
["Nie znaleziono produktów"]: ,
["Błąd przy pobieraniu danych"]: ,
["Kliknij, by przejść do formularza kontaktu"]: ,
["Cena na telefon"]: ,
["Dodany"]: ,
["Wystąpił błąd"]: ,
["Do koszyka"]: ,
["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:"]: ,
["Wystąpił błąd z inicjalizacją. Proszę odśwież stronę"]: ,
["Nie znaleziono kontenera"]: ,
["Nie znaleziono metody graphql"]: ,
["Drugie Zdjęcie"]: ,
["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 = `${omnibusDetailsTxt?.['Kod rabatowy']}`;
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 = `${omnibusDetailsTxt?.['Okazja']}`;
}
// label promocja
if (Object.keys(activeLabel)?.length === 0) {
activeLabel.bargain = `${omnibusDetailsTxt?.['Promocja']}`;
}
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: `${omnibusDetailsTxt['Najniższa cena produktu w okresie 30 dni przed wprowadzeniem obniżki']}: ${omnibusPrice}${omnibusPercent}`,
};
const max = (maxPrice) ? {
max: {
price: maxPrice,
visible: true,
percent: `-${sizeData.price.youSavePercent}%`,
html: `${omnibusDetailsTxt['Cena regularna']}:
${maxPrice}-${sizeData.price.youSavePercent}%`,
},
} : {};
const beforeRebate = (beforeRebatePrice) ? {
beforeRebate: {
price: beforeRebatePrice,
visible: !!classes.add.includes('--omnibus-code'),
percent: `-${sizeData.price.beforeRebateDetails?.youSavePercent}%`,
html: `${omnibusDetailsTxt['Cena bez kodu']}:
${beforeRebatePrice}
-${sizeData.price.beforeRebateDetails?.youSavePercent}%`,
},
} : {};
const newPriceEffectiveUntil = (newDate) ? {
newPriceEffectiveUntil: {
date: newDate,
price: maxPrice,
visible: !!classes.add.includes('--omnibus-new-price'),
html: `${omnibusDetailsTxt['Cena nadchodząca od']}
${newDate}:
${maxPrice}`,
},
} : {};
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 += `
${this.markupProductInnerHTML(prod)}
`;
return singleMarkup;
}
markupProductInnerHTML(prod){
// IDM DO POPRAWKI
const prodExchangedData = app_shop?.fn?.getOmnibusDetails?.({productData: prod}) || app_shop.fn?.idmGetOmnibusDetails({productData: prod});
return `
${this.markupAdditional(prod)}
${this.markupImage(prod)}
${this.markupLabel(prod)}
${this.markupVersions(prod)}
${this.markupOpinions(prod)}
${prod.name}
${this.markupPrice(prod, prodExchangedData)}
${this.markupAddToBasket(prod)}
`;
}
markupAdditional(prod){
return `
${this.markupCompare(prod)}
${this.markupFavorite(prod)}
`;
}
markupCompare(prod){
if(!this.options?.addToCompare) return "";
return ``
}
markupFavorite(prod){
if(!this.options?.addToFavorites || typeof this.addToFavFn !== "function") return "";
return `
`;
}
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 ``
}
markupImage(prod){
let markup = "";
if(prod.iconSmallSecond && prod.iconSecond) markup +=`
`;
else if(prod?.iconSmall) markup += `
`;
else markup += `
`;
if(this.options?.showSecondImage && prod.enclosuresImages?.[1]?.url) markup += `
`;
return markup;
}
markupLabel(prod){
let labelMarkup = ""
// labele zones
if(prod.zones?.find(zone => zone ==="bestseller")) labelMarkup += `${idmHotspotTextObject["Bestseller"]}`;
if(prod.zones?.find(zone => zone ==="news")) labelMarkup += `${idmHotspotTextObject["Nowość"]}`;
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 += `${idmHotspotTextObject["Kod rabatowy"]}`;
}else if ((!higher || newDate) && !activeLabel?.rebateCodeActive) {
labelMarkup += `${idmHotspotTextObject["Okazja"]}`;
}else {
labelMarkup += `${idmHotspotTextObject["Promocja"]}`;
}
}
return labelMarkup;
}
markupOpinions(prod){
if(!this.options?.showOpinions) return "";
return `
${prod.opinion.rating.toFixed(2)} / 5.00
`
}
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 `
${price.formatted}
/
${unit?.sellBy}
${unit?.sellBy > 1 ? unit?.plural : unit?.singular}
${convertedPrice && prod.unit?.unitConvertedFormat ? `(${convertedPrice} / ${prod.unit?.unitConvertedFormat})` : ""}
${pointsPrice ? `${pointsPrice} pkt.` : ""}
${price.value === 0 ? `${idmHotspotTextObject["Cena na telefon"]}` : ""}
${prodExchangedData?.beforeRebate?.visible ? `${prodExchangedData?.beforeRebate?.html}` : ""}
${prodExchangedData?.newPriceEffectiveUntil?.visible ? `${prodExchangedData?.newPriceEffectiveUntil?.html}` : ""}
${this.markupOmnibus(prodExchangedData?.omnibus)}
${prodExchangedData?.max?.visible ? `${prodExchangedData?.max?.html}` : ""}
`;
}
/**
* 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 `${omnibus?.html}`;
else return `
${idmHotspotTextObject["Najniższa cena"]}:
${omnibus.price}
${omnibus.percent}
${idmHotspotTextObject["Najniższa cena z 30 dni przed obniżką"]}
`;
}
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 = `Zobacz produkt`;
else if(this.options.addToBasket === "range") // +-
markup = ``;
else // Zwykłe dodanie do koszyka
markup = `
`;
return markup;
}
markupSize(prod){
return ``;
// if(!this.options?.selectSize || prod.sizes.length === 1) return ``;
// const sizesName = `${this.id}-${prod.id}`;
// return `
//
// ${prod.sizes.reduce((acc, val)=>{
// const inputId = `${sizesName}-${val.id}`;
// return acc + `
//
// `
// }, "")}
//
// `;
}
markupHotspotContainer(){
return `
${this?.title ? `
${this.title}
` : ""}
${this.markupHotspotSwiperContainer()}
`;
}
markupHotspotSwiperContainer(productsHTML=""){
return `
`;
}
// ========================================================
// 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 = `${buttonEl.dataset.success}`;
setTimeout(()=>{
buttonEl.innerHTML = `${buttonEl.dataset.text}`;
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 = `${buttonEl.dataset.error}`;
buttonEl.classList.add("--error")
setTimeout(()=>{
buttonEl.classList.remove("--error")
buttonEl.innerHTML = `${buttonEl.dataset.text}`;
}, 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", `
${this.title}
`);
}
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",
``
);
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)
})