diff --git a/README.md b/README.md
index 0c02524..b8546a0 100644
--- a/README.md
+++ b/README.md
@@ -2,55 +2,108 @@
Funkcje js składające się na customowe ramki rekomendacji
## UWAGI PRZEDWDROŻENIOWE ##
-- kod zawiera app_shop.fn.idmSetHeight używany do wyrównywania wysokości
-- kod zawiera app_shop.fn.idmGetOmnibusDetails który jest przerobionym kodem idosella app_shop.fn.getOmnibusDetails używanym w zwykłych ramkach rekomendacji
+- kod zawiera **app_shop.fn.idmGetOmnibusDetails** który jest przerobionym kodem idosella app_shop.fn.getOmnibusDetails używanym w zwykłych ramkach rekomendacji
### Pliki ###
-- bundle.js - całość
-- 1graphQL.js - graphQL + literały
-- 2funkcje.js - ogólne funkcje jak dodawanie do koszyka, czy lazy loading
-- 3markup.js - funkcje związane z markupem np zdjęć, cen
-- 4init.js - obiekt z ogólnymi ustawieniami Hotspota + init swipera
-- 5ainsertHotspotHTML.js - wstawienie ramki po kodzie html
-- 5binsertHotspotObject.js - wstawienie ramki po obiekcie js
-- 6style.css -
+- **style.css** - style wstawiane do css
+- **klasa.js** - kod js
### Użycie ###
-1. Wstawienie całego kodu do komponentu/dodatku
-2. Ustawienie defaultowych ustawień w obiekcie idmGeneralHotspotObjData
+1. Wstawienie całego kodu do komponentu (najlepiej chyba Hotspoty javascript RAYPATH - #IdoMods
+ w zwykły Javascript)/dodatku(uwaga tutaj na literały)
+2. Ustawienie defaultowych ustawień na początku klasy w **idmDefaultSwiperConfig** i w **idmDefaultHotspotOptions**
3. Wstawienie HTML lub Obiektu js z odpowienimi danymi i wywołanie funkcji od tworzenia ramek
+#### Dodatkowe informacje ####
+Można użyć extends w innym miejscu (np tym razem w wydzielonym JS) żeby nadpisać jakąś metodę, bez zmian w kodzie ramki. Będzie to przydatne w przypadku gdzie trzeba będzie zaktualizować kod ramki.
+
+**Przykład**
+```
+class IdmRaypathHotspot extends IdmHotspot {
+ markupLabel(prod) {
+ // Standardowe labelki
+ let labelMarkup = super.markupLabel(prod);
+
+ // Customowe labelki
+ const awards = prod?.awardedParameters;
+ if (awards?.length) {
+ const awardParam = awards.find(award => award.name === "Idm_custom_label");
+ const values = awardParam?.values?.map(v => v.name) || [];
+
+ const html = values
+ .map(label => {
+ const [text, bgColor, color] = label.split("||");
+ return `${text}`;
+ })
+ .join("");
+
+ labelMarkup += html;
+ }
+
+ return labelMarkup;
+ }
+}
+```
+Warto gdzieś później zapisać nową nazwę klasy np w opisie komponentu, albo w opisie szablonu.
+
+### Literały do uzupełnienia w szablonie ###
+- Coś poszło nie tak podczas dodawania do koszyka. Spróbuj ponownie lub odśwież stronę
+- Błąd przy pobieraniu danych
+- 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
#### Przykład ####
##### Jedna ramka - obiekt ######
```
-idmInsertHotspotObject({
- {
- id: "idmMainHotspot1",
- title: "Nowoczesna ramka rekomendacji",
- classes: "abcdefg",
- placement: {
- selector: "#content",
- insert: "afterbegin"
- },
- query: {
- string: `searchInput: {productsId : [589, 180, 181590,160740, 155978, 153632, 123350, 82542, 37321, 17040, 25065, 25114, 85452]}`,
- graphFn: IDM_PRODUCTS_GQL
- },
- options: {
- lazy: false,
- addToBasket: "range",
- swiper: true,
- }
- },
-})
-```
-##### Wszystkie ramki - tablica obiektów ######
-```
-idmInsertAllObjectHotspots(hotspotArr);
+new IdmHotspot({
+ id: "idmTestHotspot1",
+ title: "tescik",
+ placement: {
+ selector: "#content",
+ insert: "beforeend",
+ },
+ source: {
+ productsMenu: 1649
+ }
+});
```
+#### Wszystkie możliwe dane JS ####
+```
+/**
+ * @typedef {object} Hotspot
+ * @property {string} id - Identyfikator ramki (required).
+ * @property {string} title - Tytuł ramki.
+ * @property {string} classes - Dodatkowe klasy CSS.
+ * @property {object} placement - Określa, gdzie wstawić ramkę (required).
+ * @property {string} placement.selector - Selektor miejsca osadzenia.
+ * @property {string} placement.insert - Pozycja wstawienia względem selektora (np. "afterbegin", "beforeend").
+ * @property {object} source - Dane źródłowe dla hotspotu (required).
+ * @property {string} [source.hotspotType] - Typ hotspotu (np. "promotion").
+ * @property {number[]} [source.productsId] - Tablica ID produktów.
+ * @property {number} [source.productsMenu] - Identyfikator menu produktów.
+ * @property {object} query - Dane zapytania, nadpisują source (DEV).
+ * @property {string} query.string - Zapytanie w formacie GraphQL.
+ * @property {Function} query.graphFn - Funkcja do pobierania danych.
+ * @property {object} options - Ustawienia dla hotspotu (required).
+ * @property {boolean} options.lazy - Czy wczytywać w trybie lazy.
+ * @property {boolean|string} options.addToBasket - Obsługa koszyka:
+ * - true = włącz
+ * - false = wyłącz
+ * - "range" = dodaj z zakresem
+ * @property {boolean|object} options.swiper - Slider:
+ * - true = aktywny
+ * - false = nieaktywny
+ * - object = konfiguracja Swiper
+ * @property {Function} options.callbackFn - Funkcja callback która dzieje się po wywołaniu wszystkiego włącznie ze swiperem
+ *
+ * @type {Hotspot[]}
+ */
+```
###### Jedna ramka - HTML ######
@@ -72,52 +125,24 @@ idmInsertAllObjectHotspots(hotspotArr);
idmInsertHotspotElement(document.getElementByid("idmBlogHotspot1"));
```
-
-###### Wszystkie ramki - HTML ######
+#### Wszystkie możliwe dane HTML####
```
-idmInsertAllHTMLHotspots();
+/**
+ * Struktura sekcji hotspotu w HTML.
+ *
+ * @typedef {HTMLElement} HotspotSection
+ * @property {string} id - Identyfikator elementu (np. "idmBlogHotspot1").
+ * @property {string} class - Klasy CSS używane do stylowania.
+ *
+ * @attribute {string} data-products-id - Lista ID produktów (rozdzielona przecinkami).
+ * @attribute {number} data-products-menu - Identyfikator menu produktów.
+ * @attribute {string} data-hotspots-type - Typ hotspotu (np. "promotion").
+ * @attribute {boolean} data-lazy - Czy sekcja ma być ładowana w trybie lazy.
+ *
+ * @example
+ */
+
```
-### LISTA GLOBALNYCH FUNKCJI, ZMIENNYCH ###
-##### INIT #####
-- priceQuery
-- productQuery
-- IDM_PRODUCTS_GQL
-- IDM_HOTSPOTS_GQL
-- IDM_PRODUCT_GQL
-- IDM_HOTSPOT_ADD_TO_BASKET
-- idmHotspotTextObject
-
-#### FUNKCJE ####
-- app_shop.fn.idmGetOmnibusDetails
-- idmHandleAddToBasket
-- idmRangeMaxAlert
-- idmRangeMinAlert
-- idmQuantityButtonClick
-- idmQuantityInputChange
-- idmGetHotspotData
-- idmGetQueryData
-- idmObserveOnce
-- app_shop.fn.idmSetHeight
-
-
-#### MARKUP ####
-- idmPrepareProductsMarkup
-- idmPrepareSingleProductMarkup
-- idmPrepareHotspotImgMarkup
-- idmPrepareHotspotPriceMarkup
-- idmPrepareHotspotAddToBasketMarkup
-
-#### INIT ####
-- idmPriceType
-- idmGeneralHotspotObjData
-- idmHotspotInit
-
-#### INSERT ####
-- idmInsertHotspotElement
-- idmInsertAllHTMLHotspots
-- idmInsertHotspotObject
-- idmInsertAllObjectHotspots
-
Created by • **[IdoMods](https://idomods.pl/)** • 2025
\ No newline at end of file
diff --git a/bundle.js b/bundle.js
deleted file mode 100644
index 2e763ad..0000000
--- a/bundle.js
+++ /dev/null
@@ -1,1118 +0,0 @@
-///////////////////////////////////////////////
-// GraphQL
-// ogolne
-const priceQuery = `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 productQuery = `id
- type
- name
- zones
- icon
- iconSecond
- iconSmall
- iconSmallSecond
- link
- zones
- producer{
- name
- }
- category{
- name
- }
- sizes{
- id
- amount
- name
- ${priceQuery}
- }
- 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
- }
- ${priceQuery}`;
-// 1. products
-const IDM_PRODUCTS_GQL = (args) => JSON.stringify({
- query: `{
- products(${args}){
- took
- products{
- ${productQuery}
- }
- }
-}`
-});
-
-// 2. hotspots
-const IDM_HOTSPOTS_GQL = (args) => JSON.stringify({
- query: `{
- hotspots(${args}){
- took
- products{
- ${productQuery}
- }
- }
-}`
-});
-
-// 3. single product
-const IDM_PRODUCT_GQL = (args) => JSON.stringify({
- query: `{
- product(${args}){
- product{
- ${productQuery}
- }
- }
- }`
-});
-// 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 }`
-});
-
-
-///////////////////////////////////////////////////////////
-// TEXT
-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",
-}
-//////////////////////////////////////////////////////////////////////////\\\\\\\\\\\\
-// IDOSELL omnibus details
-
-// omnibusDetailsTxt - nadpisać na własny obiekt
-
-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 = `${idmHotspotTextObject["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 = `${idmHotspotTextObject["Okazja"]}`;
- }
- // label promocja
- if (Object.keys(activeLabel)?.length === 0) {
- activeLabel.bargain = `${idmHotspotTextObject["Promocja"]}`;
- }
-
- // labele zones
- if(productData.zones.find(zone => zone ==="bestseller")) activeLabel.bestseller = `${idmHotspotTextObject["Bestseller"]}`;
- if(productData.zones.find(zone => zone ==="news")) activeLabel.news = `${idmHotspotTextObject["Nowość"]}`;
-
-
-
- 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: `${idmHotspotTextObject['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: `${idmHotspotTextObject['Cena regularna']}:
- ${maxPrice}-${sizeData.price.youSavePercent}%`,
- },
- } : {};
-
- const beforeRebate = (beforeRebatePrice) ? {
- beforeRebate: {
- price: beforeRebatePrice,
- visible: !!classes.add.includes('--omnibus-code'),
- percent: `-${sizeData.price.beforeRebateDetails?.youSavePercent}%`,
- html: `${idmHotspotTextObject['Cena bez kodu']}:
- ${beforeRebatePrice}
- -${sizeData.price.beforeRebateDetails?.youSavePercent}%`,
- },
- } : {};
-
- const newPriceEffectiveUntil = (newDate) ? {
- newPriceEffectiveUntil: {
- date: newDate,
- price: maxPrice,
- visible: !!classes.add.includes('--omnibus-new-price'),
- html: `${idmHotspotTextObject['Cena nadchodząca od']}
- ${newDate}:
- ${maxPrice}`,
- },
- } : {};
-
-
- return {
- classes,
- omnibus,
- ...max,
- ...beforeRebate,
- ...newPriceEffectiveUntil,
- activeLabel,
- };
-};
-
-//////////////////////////////////////////////////////////////////////////
-// EVENTY
-// dodawanie do koszyka
-async function idmHandleAddToBasket(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{
- // 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 = `${buttonEl.dataset.success}`;
- setTimeout(()=>{
- buttonEl.innerHTML = `${buttonEl.dataset.text}`;
- 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 = `${buttonEl.dataset.error}`;
- buttonEl.classList.add("--error")
- setTimeout(()=>{
- buttonEl.classList.remove("--error")
- buttonEl.innerHTML = `${buttonEl.dataset.text}`;
- }, 3000);
- }finally{
- formEl.classList.remove("--loading")
- }
-}
-
-///qty
-const idmRangeMaxAlert = (max)=> Alertek.Error(`${idmHotspotTextObject["Maksymalna liczba sztuk tego towaru które możesz dodać do koszyka to:"]} ${max}`)
-const idmRangeMinAlert = (min)=> Alertek.Error(`${idmHotspotTextObject["Minimalna liczba sztuk tego towaru które możesz dodać do koszyka to:"]} ${min}`)
-
-function idmQuantityButtonClick(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;
- idmRangeMaxAlert(max)
- }
- } else if (e.target.classList.contains("idm-products-banner__qty-decrease")) {
- current -= step;
- if (current < step){
- current = step;
- idmRangeMinAlert(step)
- }
- }
- input.value = current.toFixed(precision);
-}
-function idmQuantityInputChange(e){
- if(e.target.value > +e.target.max){
- idmRangeMaxAlert(e.target.max)
- e.target.value = +e.target.max
- }
- if(e.target.value < +e.target.min){
- idmRangeMinAlert(e.target.min)
- e.target.value = +e.target.min;
- }
-}
-
-//////////////////////////////////////////////
-// DANE
-// dwie funkcje zamiast jednej
-async function idmGetHotspotData(query, graphFn){
- try{
- const res = await fetch(`/graphql/v1/`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: graphFn ? graphFn(query) : IDM_PRODUCTS_GQL(query)
- });
- const data = await res.json();
- const products = graphFn === IDM_HOTSPOTS_GQL ? data?.data?.hotspots?.products : data?.data?.products?.products;
- if(!products) throw new Error(idmHotspotTextObject["Nie znaleziono produktów"]);
-
- console.log(data);
- return products;
- }catch(err){
- console.error(idmHotspotTextObject["Błąd przy pobieraniu danych"], err);
- return null;
- }
-}
-
-
-function idmGetQueryData({
- 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}
-}
-
-//////////////////////////////////////////////
-// LAZY LOADING
-function idmObserveOnce(element, callback, options = { root: null, rootMargin: "0px", threshold: 0.1 }) {
- if (!element) return;
-
- const observer = new IntersectionObserver((entries, obs) => {
- entries.forEach(entry => {
- if (entry.isIntersecting) {
- callback(entry); // run your callback
- obs.disconnect(); // stop observing after first trigger
- }
- });
- }, options);
-
- observer.observe(element);
-}
-
-
-////////////////////////////////////////////////
-// IDM SET HEIGHT
-app_shop.fn.idmSetHeight = 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)
-}
-//////////////////////////////////////////////////////////////////////////
-// Markup
-
-// Funkcja przygotująca markup dla wszystkich produktów
-function idmPrepareProductsMarkup(products, addToBasket){
- let markup = "";
- products.forEach((prod)=>{
- markup += idmPrepareSingleProductMarkup(prod, addToBasket);
- })
- return markup;
-}
-
-// funkcja przygotowująca markup dla wybranego produktu
-function idmPrepareSingleProductMarkup(prod, addToBasket){
- const prodExchangedData = app_shop.fn?.idmGetOmnibusDetails({productData: prod});
-
- // pobranie labelek
- let labelHTMLMarkup = "";
- if(typeof prodExchangedData.activeLabel === "object") Object.entries(prodExchangedData.activeLabel).forEach(([key,value])=>{
- labelHTMLMarkup += value;
- })
-
- // markup pojedynczego produktu
- let singleMarkup = "";
- singleMarkup += `
-
`;
-
- return singleMarkup;
-}
-
-// markup zdjęcia
-function idmPrepareHotspotImgMarkup(prod){
- let markup = "";
- if(prod.iconSmallSecond !== undefined && prod.iconSecond !== undefined) markup +=`
-
-
-
-
-
- `;
- else if(prod?.iconSmall !== undefined) markup += `
-
-
-
- `;
- else markup += `
`
- return markup;
-}
-
-// markup cen
-function idmPrepareHotspotPriceMarkup(prod, prodExchangedData){
- const price = prod.price.price[idmPriceType];
- const unit = prod.unit;
- const pointsPrice = prod?.points;
- const convertedPrice = prod.price?.unitConvertedPrice?.[idmPriceType]?.formatted;
-
- return `
-
- ${price.formatted}
-
- /
- ${unit?.sellBy}
- ${unit?.sellBy > 1 ? unit?.plural : unit?.singular}
-
- ${convertedPrice ? `${convertedPrice}` : ""}
-
- ${pointsPrice ? `${pointsPrice} pkt.` : ""}
- ${price.value === 0 ? `${idmHotspotTextObject["Cena na telefon"]}` : ""}
- ${prodExchangedData?.beforeRebate?.visible ? `${prodExchangedData?.beforeRebate?.html}` : ""}
- ${prodExchangedData?.newPriceEffectiveUntil?.visible ? `${prodExchangedData?.newPriceEffectiveUntil?.html}` : ""}
- ${prodExchangedData?.omnibus?.visible ? `${prodExchangedData?.omnibus?.html}` : ""}
- ${prodExchangedData?.max?.visible ? `${prodExchangedData?.max?.html}` : ""}
- `;
-}
-
-// markup dodawania do koszyka
-function idmPrepareHotspotAddToBasketMarkup(prod, addToBasket){
- let markup = "";
- if(!addToBasket && typeof addToBasket !== "undefined" ||
- (typeof addToBasket === "undefined" && typeof idmGeneralHotspotObjData === "object" && !idmGeneralHotspotObjData?.options?.addToBasket) ||
- !addToBasket && typeof idmGeneralHotspotObjData === "undefined") return markup;
-
- // link do produktu jak nie jest to zwykły produkt
- if(prod.type !== "product") markup = `Zobacz produkt`;
- else if(addToBasket === "range"
- || typeof addToBasket === "undefined" && typeof idmGeneralHotspotObjData === "object" && idmGeneralHotspotObjData?.options?.addToBasket === "range") // +-
- markup = ``;
- else // Zwykłe dodanie do koszyka
- markup = `
- `;
- return markup;
-}
-////////////////////////////////////////////////////
-// INIT
-
-// brutto/netto
-const idmPriceType = app_shop?.vars?.priceType || "gross";
-
-
-// Zmienna trzymająca dane o ustawieniach customowych ramek rekomendacji na całym sklepie
-/**
- * Obiekt konfiguracyjny ogólnych ustawień hotspotów rekomendacji.
- *
- * @typedef {object} idmGeneralHotspotObjData
- * @property {object} options - Główne ustawienia hotspotów
- * @property {boolean} options.lazy - Czy wczytywać zawartość w trybie lazy (required).
- * @property {boolean|string} options.addToBasket - Zachowanie przy dodawaniu do koszyka:
- * - true = przycisk dodaj do koszyka
- * - false = brak przycisku
- * - "range" = dodaj z wyborem zakresu (required).
- * @property {boolean|object} options.swiper - Ustawienia slidera:
- * - true/false = włącz/wyłącz
- * - object = konfiguracja instancji Swiper (required jeśli obiekt).
- */
-const idmGeneralHotspotObjData = {
- options: {
- lazy: true,
- addToBasket: true, // true, false, "range"
- swiper: { // 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,
- },
- }
- }
- }
-}
-
-// Funkcja inicjalizująca wybranego hotspota(addtobasket range - swiper)
-async function idmHotspotInit(id, options={}){
- try{
- const hotspotEl = document.getElementById(id);
- if(!hotspotEl) throw new Error("Nie znaleziono elementu");
-
- // add to basket + -
- if(options?.addToBasket === "range" ||
- typeof options?.addToBasket === "undefined" && typeof idmGeneralHotspotObjData === "object" && idmGeneralHotspotObjData?.options?.addToBasket === "range"){
-
- // obsługa i sprawdzanie clicków
- // hotspotEl.addEventListener("click", e=>{
- // const wrapper = e.target.closest(".idm-products-banner__qty");
- // if (!wrapper) return;
-
- // if(e.target.classList.contains("idm-products-banner__qty-input")) return e.target.select();
-
- // 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;
- // rangeMaxAlert(max)
- // }
- // } else if (e.target.classList.contains("idm-products-banner__qty-decrease")) {
- // current -= step;
- // if (current < step){
- // current = step;
- // rangeMinAlert(step)
- // }
- // }
- // input.value = current.toFixed(precision);
- // });
- // sprawdzanie na input
- // hotspotEl.querySelectorAll(".idm-products-banner__qty-input").forEach(inp=>{
- // inp.addEventListener("input", e=>{
- // if(e.target.value > +e.target.max){
- // rangeMaxAlert(e.target.max)
- // e.target.value = +e.target.max
- // }
- // if(e.target.value < +e.target.min){
- // rangeMinAlert(e.target.min)
- // e.target.value = +e.target.min;
- // }
- // });
- // })
-
- }
-
- // swiper || slick
- if(options?.swiper ||
- typeof options?.swiper === "undefined" && typeof idmGeneralHotspotObjData === "object" && idmGeneralHotspotObjData?.options?.swiper){
-
-
- // Opcje swipera
- let swiperOptions = typeof options.swiper === "object" ? options.swiper : "";
- if(typeof options.swiper === "object") swiperOptions = options.swiper;
- else if(typeof idmGeneralHotspotObjData === "object" && typeof idmGeneralHotspotObjData?.options?.swiper === "object"){
- swiperOptions = idmGeneralHotspotObjData?.options?.swiper;
- swiperOptions.navigation = {
- nextEl: `#${id} .idm-button-next`,
- prevEl: `#${id} .idm-button-prev`,
- }
- }else{
- swiperOptions = {
- 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,
- },
- },
- navigation: {
- nextEl: `#${id} .idm-button-next`,
- prevEl: `#${id} .idm-button-prev`,
- },
- }
- }
-
- // Wywołanie swipera
- const selectedSwiper = new HotspotSlider({
- selector: `#${id} .swiper`,
- hotspotName: `${id}`,
- options: swiperOptions,
- });
- await selectedSwiper.init();
- }
-
-
- if(typeof options?.callbackFn === "function") options?.callbackFn();
-
- // IDM setHeight
- app_shop.fn.idmSetHeight({
- selectors: [
- `#${id} .product__prices`,
- `#${id} .product__name`,
- ],
- container: `#${id} .products__wrapper`,
- });
- console.log(`Initialized hotspot #${id}`);
- }catch(err){
- console.error(idmHotspotTextObject["Wystąpił błąd z inicjalizacją. Proszę odśwież stronę"], err);
- }
-}
-
-////////////////////////////////////////////////////
-// Funkcja init ELEMENT HTML
-async function idmInsertHotspotElement(selectedContainerEl){
-
- selectedContainerEl.classList.add("--init");
- const {graphFn, query} = idmGetQueryData({
- productsID: selectedContainerEl?.dataset?.productsId,
- productsMenu: selectedContainerEl?.dataset?.productsMenu,
- hotspotsType: selectedContainerEl.dataset.hotspotsType
- });
-
- if(!graphFn || !query){
- console.log(idmHotspotTextObject["Nie znaleziono metody graphql"], selectedContainerEl)
- return selectedContainerEl.remove();
- }
-
- // Funkcja od uzupełniania danych
- const idmFill = async ()=>{
- try{
- // pobranie danych o produktach
- const products = await idmGetHotspotData(query, graphFn);
- if(!products) throw new Error(idmHotspotTextObject["Nie znaleziono produktów"]);
-
-
- // wstawienie produktów zależnie czy w section jest .hotspot czy nie
- const hotspotInsideEl = selectedContainerEl.querySelector(".hotspot");
- if(hotspotInsideEl) hotspotInsideEl.innerHTML += `
-
- ${idmPrepareProductsMarkup(products, true)}
-
-
-
-
- `;
- else selectedContainerEl.innerHTML = `
-
- ${idmPrepareProductsMarkup(products, true)}
-
-
-
-
-
`;
-
- selectedContainerEl.classList.remove("idm-loading")
- // init swipera
- idmHotspotInit(selectedContainerEl.id)
- }catch(err){
- console.error(idmHotspotTextObject["Wystąpił błąd"], err);
- selectedContainerEl.remove();
- }
- }
-
-
- if(selectedContainerEl.dataset?.lazy ||
- !selectedContainerEl.dataset?.lazy && typeof idmGeneralHotspotObjData === "object" && idmGeneralHotspotObjData?.options?.lazy) idmObserveOnce(selectedContainerEl, idmFill);
- else idmFill();
-}
-
-
-
-
-
-// Zebranie wszystkich ramek HTML i wstawienie ich.
-async function idmInsertAllHTMLHotspots(){
- try{
- const reqArr = []
- document.querySelectorAll(".idm__hotspot:not(.--init):not(.--lazy-hotspot)").forEach(hotspot=>{
- reqArr.push(idmInsertHotspotElement(hotspot));
- });
- await Promise.all(reqArr);
- }catch(err){
- console.error(err)
- }
-}
-
-idmInsertAllHTMLHotspots();
-
-/////////////////////////////////////////////////////////////////////
-// wdrożenie dla elementu HTML
-/**
- * Struktura sekcji hotspotu w HTML.
- *
- * @typedef {HTMLElement} HotspotSection
- * @property {string} id - Identyfikator elementu (np. "idmBlogHotspot1").
- * @property {string} class - Klasy CSS używane do stylowania.
- *
- * @attribute {string} data-products-id - Lista ID produktów (rozdzielona przecinkami).
- * @attribute {number} data-products-menu - Identyfikator menu produktów.
- * @attribute {string} data-hotspots-type - Typ hotspotu (np. "promotion").
- * @attribute {boolean} data-lazy - Czy sekcja ma być ładowana w trybie lazy.
- *
- * @example
-
- */
-////////////////////////////////////////////////////
-// Funkcja init OBIEKT JS
-async function idmInsertHotspotObject(idmHotspotObj){
- // Wstaw kontener
- const selectedEl = document.querySelector(idmHotspotObj?.placement.selector);
- if(!selectedEl) throw new Error(idmHotspotTextObject["Nie znaleziono kontenera"]);
-
- selectedEl.insertAdjacentHTML(idmHotspotObj.placement.insert || "afterend", `
-
- ${idmHotspotObj?.title ? `
-
- ${idmHotspotObj.title}
-
- ` : ""}
-
-
-
-
-
- `);
-
-
- // Utworzenie markupa HTML
- const selectedContainerEl = document.getElementById(idmHotspotObj.id);
- if(!selectedContainerEl) throw new Error(idmHotspotTextObject["Nie znaleziono kontenera"]);
-
- const idmFill = async ()=>{
- try{
- let {graphFn, query} = idmGetQueryData({
- productsID: idmHotspotObj?.source?.productsId,
- productsMenu: idmHotspotObj?.source?.productsMenu,
- hotspotsType: idmHotspotObj?.source?.hotspotsType
- });
-
- if(idmHotspotObj?.query?.graphFn && idmHotspotObj?.query?.string){
- graphFn = idmHotspotObj.query.graphFn;
- query = idmHotspotObj.query.string;
- }
-
- // pobranie danych o produktach
- const products = await idmGetHotspotData(query, graphFn);
- if(!products || !products.length) throw new Error(idmHotspotTextObject["Nie znaleziono produktów"]);
-
-
- // Wstawienie markupa na strone
- const hotspotMarkup = `${idmPrepareProductsMarkup(products, idmHotspotObj?.options?.addToBasket)}`;
- selectedContainerEl.querySelector(".products.hotspot__products")?.insertAdjacentHTML("beforeend", hotspotMarkup);
- selectedContainerEl.classList.remove("idm-loading");
-
- // init swiper + add to basket
- idmHotspotInit(idmHotspotObj.id, idmHotspotObj?.options)
- }catch(err){
- console.error(idmHotspotTextObject["Wystąpił błąd"], err);
- selectedContainerEl.remove();
- }
- }
-
-
- if(idmHotspotObj?.options?.lazy ||
- typeof idmHotspotObj?.options?.lazy === "undefined" && typeof idmGeneralHotspotObjData === "object" && idmGeneralHotspotObjData?.options?.lazy) idmObserveOnce(selectedContainerEl, idmFill);
- else idmFill();
-}
-
-
-// obiekt js z przykładowymi danymi
-/**
- * Tablica konfiguracji hotspotów rekomendacji.
- *
- * @typedef {object} Hotspot
- * @property {string} id - Identyfikator ramki (required).
- * @property {string} title - Tytuł ramki.
- * @property {string} classes - Dodatkowe klasy CSS.
- * @property {object} placement - Określa, gdzie wstawić ramkę (required).
- * @property {string} placement.selector - Selektor miejsca osadzenia.
- * @property {string} placement.insert - Pozycja wstawienia względem selektora (np. "afterbegin", "beforeend").
- * @property {object} source - Dane źródłowe dla hotspotu (required).
- * @property {string} [source.hotspotType] - Typ hotspotu (np. "promotion").
- * @property {number[]} [source.productsId] - Tablica ID produktów.
- * @property {number} [source.productsMenu] - Identyfikator menu produktów.
- * @property {object} query - Dane zapytania, nadpisują source (DEV).
- * @property {string} query.string - Zapytanie w formacie GraphQL.
- * @property {Function} query.graphFn - Funkcja do pobierania danych.
- * @property {object} options - Ustawienia dla hotspotu (required).
- * @property {boolean} options.lazy - Czy wczytywać w trybie lazy.
- * @property {boolean|string} options.addToBasket - Obsługa koszyka:
- * - true = włącz
- * - false = wyłącz
- * - "range" = dodaj z zakresem
- * @property {boolean|object} options.swiper - Slider:
- * - true = aktywny
- * - false = nieaktywny
- * - object = konfiguracja Swiper
- * @property {Function} options.callbackFn - Funkcja callback która dzieje się po wywołaniu wszystkiego włącznie ze swiperem
- *
- * @type {Hotspot[]}
- */
-// const idmHotspotArr = [
-// {
-// id: "idmMainHotspot1",//!id ramki
-// title: "Nowoczesna ramka rekomendacji",//
-// classes: "abcdefg",
-// placement: {
-// selector: "#content",
-// insert: "afterbegin"
-// },
-// source: {
-// hotspotType: "protomtion",
-// productsId: [11,12],
-// productsMenu: 122,
-// },
-// query: {
-// string: `searchInput: {productsId : [589, 180, 181590,160740, 155978, 153632, 123350, 82542, 37321, 17040, 25065, 25114, 85452]}`,
-// graphFn: IDM_PRODUCTS_GQL
-// },
-// // addToBasket: true,
-// options: {
-// lazy: false,
-// addToBasket: "range",
-// swiper: true,
-// callbackFn: ()=>{console.log("test")}
-// // swiper: albo true false - albo obiekt z opcjami swipera
-// }
-// },
-// {
-// id: "idmMainHotspot2",
-// title: "Super ramka rekomendacji",
-// placement: {
-// selector: "#content",
-// insert: "beforeend"
-// },
-// source: {
-// productsMenu: 488,
-// },
-// query: {
-// string: `searchInput: {hotspot: promotion,limit: 16}`,
-// graphFn: IDM_HOTSPOTS_GQL
-// },
-// // addToBasket: true,
-// options: {
-// lazy: true,
-// addToBasket: "range",
-// swiper: true,
-// // swiper: albo true false - albo obiekt z opcjami swipera
-// }
-// }
-// ];
-
-
-// Wrzucenie na strone wszystkich ramek z obiektu js
-async function idmInsertAllObjectHotspots(hotspotArr){
- try{
- const reqArr = []
- hotspotArr.forEach(hotspotObj=>{
- reqArr.push(idmInsertHotspotObject(hotspotObj));
- });
- await Promise.all(reqArr);
- }catch(err){
- console.error(err)
- }
-}
-
-
-// idmInsertAllObjectHotspots(idmHotspotArr);
diff --git a/klasa.js b/klasa.js
new file mode 100644
index 0000000..b050e78
--- /dev/null
+++ b/klasa.js
@@ -0,0 +1,1040 @@
+///////////////////////////////////////////////////////////
+// TEXT
+// LITERAŁY
+const idmHotspotTextObject = {
+ []: ,
+ []: ,
+ []: ,
+ []: ,
+ []: ,
+ []: ,
+ []: ,
+ []: ,
+ []: ,
+ []: ,
+ []: ,
+ []: ,
+ []: ,
+ []: ,
+ []: ,
+ []: ,
+ []: ,
+ []: ,
+ []: ,
+ []: ,
+ []: ,
+ []: ,
+ []: ,
+ []: ,
+ []: ,
+};
+// 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 = `${idmHotspotTextObject["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 = `${idmHotspotTextObject["Okazja"]}`;
+ }
+ // label promocja
+ if (Object.keys(activeLabel)?.length === 0) {
+ activeLabel.bargain = `${idmHotspotTextObject["Promocja"]}`;
+ }
+
+ // // labele zones
+ // if(productData.zones.find(zone => zone ==="bestseller")) activeLabel.bestseller = `${idmHotspotTextObject["Bestseller"]}`;
+ // if(productData.zones.find(zone => zone ==="news")) activeLabel.news = `${idmHotspotTextObject["Nowość"]}`;
+
+
+
+ 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: `${idmHotspotTextObject['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: `${idmHotspotTextObject['Cena regularna']}:
+ ${maxPrice}-${sizeData.price.youSavePercent}%`,
+ },
+ } : {};
+
+ const beforeRebate = (beforeRebatePrice) ? {
+ beforeRebate: {
+ price: beforeRebatePrice,
+ visible: !!classes.add.includes('--omnibus-code'),
+ percent: `-${sizeData.price.beforeRebateDetails?.youSavePercent}%`,
+ html: `${idmHotspotTextObject['Cena bez kodu']}:
+ ${beforeRebatePrice}
+ -${sizeData.price.beforeRebateDetails?.youSavePercent}%`,
+ },
+ } : {};
+
+ const newPriceEffectiveUntil = (newDate) ? {
+ newPriceEffectiveUntil: {
+ date: newDate,
+ price: maxPrice,
+ visible: !!classes.add.includes('--omnibus-new-price'),
+ html: `${idmHotspotTextObject['Cena nadchodząca od']}
+ ${newDate}:
+ ${maxPrice}`,
+ },
+ } : {};
+
+
+ return {
+ classes,
+ omnibus,
+ ...max,
+ ...beforeRebate,
+ ...newPriceEffectiveUntil,
+ activeLabel,
+ };
+};
+
+/**
+ * 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"
+ swiper: true,
+ callbackFn: ()=>{}
+ }
+ }
+ /**
+ * 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?.idmGetOmnibusDetails({productData: prod});
+
+ // markup pojedynczego produktu
+ let singleMarkup = "";
+ singleMarkup += `
+ `;
+
+ return singleMarkup;
+ }
+
+
+ markupImage(prod){
+ let markup = "";
+ if(prod.iconSmallSecond !== undefined && prod.iconSecond !== undefined) markup +=`
+
+
+
+
+
+ `;
+ else if(prod?.iconSmall !== undefined) markup += `
+
+
+
+ `;
+ else 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;
+ }
+ 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 ? `${convertedPrice}` : ""}
+
+ ${pointsPrice ? `${pointsPrice} pkt.` : ""}
+ ${price.value === 0 ? `${idmHotspotTextObject["Cena na telefon"]}` : ""}
+ ${prodExchangedData?.beforeRebate?.visible ? `${prodExchangedData?.beforeRebate?.html}` : ""}
+ ${prodExchangedData?.newPriceEffectiveUntil?.visible ? `${prodExchangedData?.newPriceEffectiveUntil?.html}` : ""}
+ ${prodExchangedData?.omnibus?.visible ? `${prodExchangedData?.omnibus?.html}` : ""}
+ ${prodExchangedData?.max?.visible ? `${prodExchangedData?.max?.html}` : ""}
+ `;
+ }
+
+ 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;
+ }
+
+ markupHotspotContainer(){
+ return `
+
+ ${this?.title ? `
+
+ ${this.title}
+
+ ` : ""}
+
+
+
+
+
+ `;
+ }
+
+ // ========================================================
+ // 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{
+ // 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 = `${buttonEl.dataset.success}`;
+ setTimeout(()=>{
+ buttonEl.innerHTML = `${buttonEl.dataset.text}`;
+ 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 = `${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 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);
+ }
+
+ // ========================================================
+ // 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", `
+ ${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);
+ }
+ }
+
+ /**
+ * Pobiera dane, wypełnia markup i inicjuje Swipera.
+ */
+ async fillHotspot(){
+ 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();
+ }
+ }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);
+ }
+ }
+ }
+
+ /**
+ * 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();
+ }
+}
+
+// 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)
+})
diff --git a/sklad/1graphQL.js b/sklad/1graphQL.js
deleted file mode 100644
index 60f803d..0000000
--- a/sklad/1graphQL.js
+++ /dev/null
@@ -1,207 +0,0 @@
-///////////////////////////////////////////////
-// GraphQL
-// ogolne
-const priceQuery = `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 productQuery = `id
- type
- name
- zones
- icon
- iconSecond
- iconSmall
- iconSmallSecond
- link
- zones
- producer{
- name
- }
- category{
- name
- }
- sizes{
- id
- amount
- name
- ${priceQuery}
- }
- 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
- }
- ${priceQuery}`;
-// 1. products
-const IDM_PRODUCTS_GQL = (args) => JSON.stringify({
- query: `{
- products(${args}){
- took
- products{
- ${productQuery}
- }
- }
-}`
-});
-
-// 2. hotspots
-const IDM_HOTSPOTS_GQL = (args) => JSON.stringify({
- query: `{
- hotspots(${args}){
- took
- products{
- ${productQuery}
- }
- }
-}`
-});
-
-// 3. single product
-const IDM_PRODUCT_GQL = (args) => JSON.stringify({
- query: `{
- product(${args}){
- product{
- ${productQuery}
- }
- }
- }`
-});
-// 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 }`
-});
-
-
-///////////////////////////////////////////////////////////
-// TEXT
-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",
-}
\ No newline at end of file
diff --git a/sklad/2funkcje.js b/sklad/2funkcje.js
deleted file mode 100644
index 54e3907..0000000
--- a/sklad/2funkcje.js
+++ /dev/null
@@ -1,321 +0,0 @@
-//////////////////////////////////////////////////////////////////////////\\\\\\\\\\\\
-// IDOSELL omnibus details
-
-// omnibusDetailsTxt - nadpisać na własny obiekt
-
-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 = `${idmHotspotTextObject["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 = `${idmHotspotTextObject["Okazja"]}`;
- }
- // label promocja
- if (Object.keys(activeLabel)?.length === 0) {
- activeLabel.bargain = `${idmHotspotTextObject["Promocja"]}`;
- }
-
- // labele zones
- if(productData.zones.find(zone => zone ==="bestseller")) activeLabel.bestseller = `${idmHotspotTextObject["Bestseller"]}`;
- if(productData.zones.find(zone => zone ==="news")) activeLabel.news = `${idmHotspotTextObject["Nowość"]}`;
-
-
-
- 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: `${idmHotspotTextObject['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: `${idmHotspotTextObject['Cena regularna']}:
- ${maxPrice}-${sizeData.price.youSavePercent}%`,
- },
- } : {};
-
- const beforeRebate = (beforeRebatePrice) ? {
- beforeRebate: {
- price: beforeRebatePrice,
- visible: !!classes.add.includes('--omnibus-code'),
- percent: `-${sizeData.price.beforeRebateDetails?.youSavePercent}%`,
- html: `${idmHotspotTextObject['Cena bez kodu']}:
- ${beforeRebatePrice}
- -${sizeData.price.beforeRebateDetails?.youSavePercent}%`,
- },
- } : {};
-
- const newPriceEffectiveUntil = (newDate) ? {
- newPriceEffectiveUntil: {
- date: newDate,
- price: maxPrice,
- visible: !!classes.add.includes('--omnibus-new-price'),
- html: `${idmHotspotTextObject['Cena nadchodząca od']}
- ${newDate}:
- ${maxPrice}`,
- },
- } : {};
-
-
- return {
- classes,
- omnibus,
- ...max,
- ...beforeRebate,
- ...newPriceEffectiveUntil,
- activeLabel,
- };
-};
-
-//////////////////////////////////////////////////////////////////////////
-// EVENTY
-// dodawanie do koszyka
-async function idmHandleAddToBasket(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{
- // 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 = `${buttonEl.dataset.success}`;
- setTimeout(()=>{
- buttonEl.innerHTML = `${buttonEl.dataset.text}`;
- 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 = `${buttonEl.dataset.error}`;
- buttonEl.classList.add("--error")
- setTimeout(()=>{
- buttonEl.classList.remove("--error")
- buttonEl.innerHTML = `${buttonEl.dataset.text}`;
- }, 3000);
- }finally{
- formEl.classList.remove("--loading")
- }
-}
-
-///qty
-const idmRangeMaxAlert = (max)=> Alertek.Error(`${idmHotspotTextObject["Maksymalna liczba sztuk tego towaru które możesz dodać do koszyka to:"]} ${max}`)
-const idmRangeMinAlert = (min)=> Alertek.Error(`${idmHotspotTextObject["Minimalna liczba sztuk tego towaru które możesz dodać do koszyka to:"]} ${min}`)
-
-function idmQuantityButtonClick(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;
- idmRangeMaxAlert(max)
- }
- } else if (e.target.classList.contains("idm-products-banner__qty-decrease")) {
- current -= step;
- if (current < step){
- current = step;
- idmRangeMinAlert(step)
- }
- }
- input.value = current.toFixed(precision);
-}
-function idmQuantityInputChange(e){
- if(e.target.value > +e.target.max){
- idmRangeMaxAlert(e.target.max)
- e.target.value = +e.target.max
- }
- if(e.target.value < +e.target.min){
- idmRangeMinAlert(e.target.min)
- e.target.value = +e.target.min;
- }
-}
-
-//////////////////////////////////////////////
-// DANE
-// dwie funkcje zamiast jednej
-async function idmGetHotspotData(query, graphFn){
- try{
- const res = await fetch(`/graphql/v1/`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: graphFn ? graphFn(query) : IDM_PRODUCTS_GQL(query)
- });
- const data = await res.json();
- const products = graphFn === IDM_HOTSPOTS_GQL ? data?.data?.hotspots?.products : data?.data?.products?.products;
- if(!products || !products.length) throw new Error(idmHotspotTextObject["Nie znaleziono produktów"]);
-
- console.log(data);
- return products;
- }catch(err){
- console.error(idmHotspotTextObject["Błąd przy pobieraniu danych"], err);
- return null;
- }
-}
-
-
-function idmGetQueryData({
- 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}
-}
-
-//////////////////////////////////////////////
-// LAZY LOADING
-function idmObserveOnce(element, callback, options = { root: null, rootMargin: "0px", threshold: 0.1 }) {
- if (!element) return;
-
- const observer = new IntersectionObserver((entries, obs) => {
- entries.forEach(entry => {
- if (entry.isIntersecting) {
- callback(entry); // run your callback
- obs.disconnect(); // stop observing after first trigger
- }
- });
- }, options);
-
- observer.observe(element);
-}
-
-
-////////////////////////////////////////////////
-// IDM SET HEIGHT
-app_shop.fn.idmSetHeight = 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)
-}
\ No newline at end of file
diff --git a/sklad/3markup.js b/sklad/3markup.js
deleted file mode 100644
index 98b6a42..0000000
--- a/sklad/3markup.js
+++ /dev/null
@@ -1,147 +0,0 @@
-//////////////////////////////////////////////////////////////////////////
-// Markup
-
-// Funkcja przygotująca markup dla wszystkich produktów
-function idmPrepareProductsMarkup(products, addToBasket){
- let markup = "";
- products.forEach((prod)=>{
- markup += idmPrepareSingleProductMarkup(prod, addToBasket);
- })
- return markup;
-}
-
-// funkcja przygotowująca markup dla wybranego produktu
-function idmPrepareSingleProductMarkup(prod, addToBasket){
- const prodExchangedData = app_shop.fn?.idmGetOmnibusDetails({productData: prod});
-
- // pobranie labelek
- let labelHTMLMarkup = "";
- if(typeof prodExchangedData.activeLabel === "object") Object.entries(prodExchangedData.activeLabel).forEach(([key,value])=>{
- labelHTMLMarkup += value;
- })
-
- // markup pojedynczego produktu
- let singleMarkup = "";
- singleMarkup += `
- `;
-
- return singleMarkup;
-}
-
-// markup zdjęcia
-function idmPrepareHotspotImgMarkup(prod){
- let markup = "";
- if(prod.iconSmallSecond !== undefined && prod.iconSecond !== undefined) markup +=`
-
-
-
-
-
- `;
- else if(prod?.iconSmall !== undefined) markup += `
-
-
-
- `;
- else markup += `
`
- return markup;
-}
-
-// markup cen
-function idmPrepareHotspotPriceMarkup(prod, prodExchangedData){
- const price = prod.price.price[idmPriceType];
- const unit = prod.unit;
- const pointsPrice = prod?.points;
- const convertedPrice = prod.price?.unitConvertedPrice?.[idmPriceType]?.formatted;
-
- return `
-
- ${price.formatted}
-
- /
- ${unit?.sellBy}
- ${unit?.sellBy > 1 ? unit?.plural : unit?.singular}
-
- ${convertedPrice ? `${convertedPrice}` : ""}
-
- ${pointsPrice ? `${pointsPrice} pkt.` : ""}
- ${price.value === 0 ? `${idmHotspotTextObject["Cena na telefon"]}` : ""}
- ${prodExchangedData?.beforeRebate?.visible ? `${prodExchangedData?.beforeRebate?.html}` : ""}
- ${prodExchangedData?.newPriceEffectiveUntil?.visible ? `${prodExchangedData?.newPriceEffectiveUntil?.html}` : ""}
- ${prodExchangedData?.omnibus?.visible ? `${prodExchangedData?.omnibus?.html}` : ""}
- ${prodExchangedData?.max?.visible ? `${prodExchangedData?.max?.html}` : ""}
- `;
-}
-
-// markup dodawania do koszyka
-function idmPrepareHotspotAddToBasketMarkup(prod, addToBasket){
- let markup = "";
- if(!addToBasket && typeof addToBasket !== "undefined" ||
- (typeof addToBasket === "undefined" && typeof idmGeneralHotspotObjData === "object" && !idmGeneralHotspotObjData?.options?.addToBasket) ||
- !addToBasket && typeof idmGeneralHotspotObjData === "undefined") return markup;
-
- // link do produktu jak nie jest to zwykły produkt
- if(prod.type !== "product") markup = `Zobacz produkt`;
- else if(addToBasket === "range"
- || typeof addToBasket === "undefined" && typeof idmGeneralHotspotObjData === "object" && idmGeneralHotspotObjData?.options?.addToBasket === "range") // +-
- markup = ``;
- else // Zwykłe dodanie do koszyka
- markup = `
- `;
- return markup;
-}
diff --git a/sklad/4init.js b/sklad/4init.js
deleted file mode 100644
index 1f9964a..0000000
--- a/sklad/4init.js
+++ /dev/null
@@ -1,167 +0,0 @@
-////////////////////////////////////////////////////
-// INIT
-
-// brutto/netto
-const idmPriceType = app_shop?.vars?.priceType || "gross";
-
-
-// Zmienna trzymająca dane o ustawieniach customowych ramek rekomendacji na całym sklepie
-/**
- * Obiekt konfiguracyjny ogólnych ustawień hotspotów rekomendacji.
- *
- * @typedef {object} idmGeneralHotspotObjData
- * @property {object} options - Główne ustawienia hotspotów
- * @property {boolean} options.lazy - Czy wczytywać zawartość w trybie lazy (required).
- * @property {boolean|string} options.addToBasket - Zachowanie przy dodawaniu do koszyka:
- * - true = przycisk dodaj do koszyka
- * - false = brak przycisku
- * - "range" = dodaj z wyborem zakresu (required).
- * @property {boolean|object} options.swiper - Ustawienia slidera:
- * - true/false = włącz/wyłącz
- * - object = konfiguracja instancji Swiper (required jeśli obiekt).
- */
-const idmGeneralHotspotObjData = {
- options: {
- lazy: true,
- addToBasket: true, // true, false, "range"
- swiper: { // 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,
- },
- }
- }
- }
-}
-
-// Funkcja inicjalizująca wybranego hotspota(addtobasket range - swiper)
-async function idmHotspotInit(id, options={}){
- try{
- const hotspotEl = document.getElementById(id);
- if(!hotspotEl) throw new Error("Nie znaleziono elementu");
-
- // add to basket + -
- if(options?.addToBasket === "range" ||
- typeof options?.addToBasket === "undefined" && typeof idmGeneralHotspotObjData === "object" && idmGeneralHotspotObjData?.options?.addToBasket === "range"){
-
- // obsługa i sprawdzanie clicków
- // hotspotEl.addEventListener("click", e=>{
- // const wrapper = e.target.closest(".idm-products-banner__qty");
- // if (!wrapper) return;
-
- // if(e.target.classList.contains("idm-products-banner__qty-input")) return e.target.select();
-
- // 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;
- // rangeMaxAlert(max)
- // }
- // } else if (e.target.classList.contains("idm-products-banner__qty-decrease")) {
- // current -= step;
- // if (current < step){
- // current = step;
- // rangeMinAlert(step)
- // }
- // }
- // input.value = current.toFixed(precision);
- // });
- // sprawdzanie na input
- // hotspotEl.querySelectorAll(".idm-products-banner__qty-input").forEach(inp=>{
- // inp.addEventListener("input", e=>{
- // if(e.target.value > +e.target.max){
- // rangeMaxAlert(e.target.max)
- // e.target.value = +e.target.max
- // }
- // if(e.target.value < +e.target.min){
- // rangeMinAlert(e.target.min)
- // e.target.value = +e.target.min;
- // }
- // });
- // })
-
- }
-
- // swiper || slick
- if(options?.swiper ||
- typeof options?.swiper === "undefined" && typeof idmGeneralHotspotObjData === "object" && idmGeneralHotspotObjData?.options?.swiper){
-
-
- // Opcje swipera
- let swiperOptions = typeof options.swiper === "object" ? options.swiper : "";
- if(typeof options.swiper === "object") swiperOptions = options.swiper;
- else if(typeof idmGeneralHotspotObjData === "object" && typeof idmGeneralHotspotObjData?.options?.swiper === "object"){
- swiperOptions = idmGeneralHotspotObjData?.options?.swiper;
- swiperOptions.navigation = {
- nextEl: `#${id} .idm-button-next`,
- prevEl: `#${id} .idm-button-prev`,
- }
- }else{
- swiperOptions = {
- 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,
- },
- },
- navigation: {
- nextEl: `#${id} .idm-button-next`,
- prevEl: `#${id} .idm-button-prev`,
- },
- }
- }
-
- // Wywołanie swipera
- const selectedSwiper = new HotspotSlider({
- selector: `#${id} .swiper`,
- hotspotName: `${id}`,
- options: swiperOptions,
- });
- await selectedSwiper.init();
- }
-
-
- if(typeof options?.callbackFn === "function") options?.callbackFn();
-
- // IDM setHeight
- app_shop.fn.idmSetHeight({
- selectors: [
- `#${id} .product__prices`,
- `#${id} .product__name`,
- ],
- container: `#${id} .products__wrapper`,
- });
- console.log(`Initialized hotspot #${id}`);
- }catch(err){
- console.error(idmHotspotTextObject["Wystąpił błąd z inicjalizacją. Proszę odśwież stronę"], err);
- }
-}
\ No newline at end of file
diff --git a/sklad/5ainsertHotspotHTML.js b/sklad/5ainsertHotspotHTML.js
deleted file mode 100644
index 604d74d..0000000
--- a/sklad/5ainsertHotspotHTML.js
+++ /dev/null
@@ -1,109 +0,0 @@
-
-////////////////////////////////////////////////////
-// Funkcja init ELEMENT HTML
-async function idmInsertHotspotElement(selectedContainerEl){
-
- selectedContainerEl.classList.add("--init");
- const {graphFn, query} = idmGetQueryData({
- productsID: selectedContainerEl?.dataset?.productsId,
- productsMenu: selectedContainerEl?.dataset?.productsMenu,
- hotspotsType: selectedContainerEl.dataset.hotspotsType
- });
-
- if(!graphFn || !query){
- console.log(idmHotspotTextObject["Nie znaleziono metody graphql"], selectedContainerEl)
- return selectedContainerEl.remove();
- }
-
- // Funkcja od uzupełniania danych
- const idmFill = async ()=>{
- try{
- // pobranie danych o produktach
- const products = await idmGetHotspotData(query, graphFn);
- if(!products) throw new Error(idmHotspotTextObject["Nie znaleziono produktów"]);
-
-
- // wstawienie produktów zależnie czy w section jest .hotspot czy nie
- const hotspotInsideEl = selectedContainerEl.querySelector(".hotspot");
- if(hotspotInsideEl) hotspotInsideEl.innerHTML += `
-
- ${idmPrepareProductsMarkup(products, true)}
-
-
-
-
- `;
- else selectedContainerEl.innerHTML = `
-
- ${idmPrepareProductsMarkup(products, true)}
-
-
-
-
-
`;
-
- selectedContainerEl.classList.remove("idm-loading")
- // init swipera
- idmHotspotInit(selectedContainerEl.id)
- }catch(err){
- console.error(idmHotspotTextObject["Wystąpił błąd"], err);
- selectedContainerEl.remove();
- }
- }
-
-
- if(selectedContainerEl.dataset?.lazy ||
- !selectedContainerEl.dataset?.lazy && typeof idmGeneralHotspotObjData === "object" && idmGeneralHotspotObjData?.options?.lazy) idmObserveOnce(selectedContainerEl, idmFill);
- else idmFill();
-}
-
-
-
-
-
-// Zebranie wszystkich ramek HTML i wstawienie ich.
-async function idmInsertAllHTMLHotspots(){
- try{
- const reqArr = []
- document.querySelectorAll(".idm__hotspot:not(.--init):not(.--lazy-hotspot)").forEach(hotspot=>{
- reqArr.push(idmInsertHotspotElement(hotspot));
- });
- await Promise.all(reqArr);
- }catch(err){
- console.error(err)
- }
-}
-
-idmInsertAllHTMLHotspots();
-
-/////////////////////////////////////////////////////////////////////
-// wdrożenie dla elementu HTML
-/**
- * Struktura sekcji hotspotu w HTML.
- *
- * @typedef {HTMLElement} HotspotSection
- * @property {string} id - Identyfikator elementu (np. "idmBlogHotspot1").
- * @property {string} class - Klasy CSS używane do stylowania.
- *
- * @attribute {string} data-products-id - Lista ID produktów (rozdzielona przecinkami).
- * @attribute {number} data-products-menu - Identyfikator menu produktów.
- * @attribute {string} data-hotspots-type - Typ hotspotu (np. "promotion").
- * @attribute {boolean} data-lazy - Czy sekcja ma być ładowana w trybie lazy.
- *
- * @example
-
- */
diff --git a/sklad/5binsertHotspotObject.js b/sklad/5binsertHotspotObject.js
deleted file mode 100644
index 2a4ffa4..0000000
--- a/sklad/5binsertHotspotObject.js
+++ /dev/null
@@ -1,167 +0,0 @@
-////////////////////////////////////////////////////
-// Funkcja init OBIEKT JS
-async function idmInsertHotspotObject(idmHotspotObj){
- // Wstaw kontener
- const selectedEl = document.querySelector(idmHotspotObj?.placement.selector);
- if(!selectedEl) throw new Error(idmHotspotTextObject["Nie znaleziono kontenera"]);
-
- selectedEl.insertAdjacentHTML(idmHotspotObj.placement.insert || "afterend", `
-
- ${idmHotspotObj?.title ? `
-
- ${idmHotspotObj.title}
-
- ` : ""}
-
-
-
-
-
- `);
-
-
- // Utworzenie markupa HTML
- const selectedContainerEl = document.getElementById(idmHotspotObj.id);
- if(!selectedContainerEl) throw new Error(idmHotspotTextObject["Nie znaleziono kontenera"]);
-
- const idmFill = async ()=>{
- try{
- let {graphFn, query} = idmGetQueryData({
- productsID: idmHotspotObj?.source?.productsId,
- productsMenu: idmHotspotObj?.source?.productsMenu,
- hotspotsType: idmHotspotObj?.source?.hotspotsType
- });
-
- if(idmHotspotObj?.query?.graphFn && idmHotspotObj?.query?.string){
- graphFn = idmHotspotObj.query.graphFn;
- query = idmHotspotObj.query.string;
- }
-
- // pobranie danych o produktach
- const products = await idmGetHotspotData(query, graphFn);
- if(!products) throw new Error(idmHotspotTextObject["Nie znaleziono produktów"]);
-
-
- // Wstawienie markupa na strone
- const hotspotMarkup = `${idmPrepareProductsMarkup(products, idmHotspotObj?.options?.addToBasket)}`;
- selectedContainerEl.querySelector(".products.hotspot__products")?.insertAdjacentHTML("beforeend", hotspotMarkup);
- selectedContainerEl.classList.remove("idm-loading");
-
- // init swiper + add to basket
- idmHotspotInit(idmHotspotObj.id, idmHotspotObj?.options)
- }catch(err){
- console.error(idmHotspotTextObject["Wystąpił błąd"], err);
- selectedContainerEl.remove();
- }
- }
-
-
- if(idmHotspotObj?.options?.lazy ||
- typeof idmHotspotObj?.options?.lazy === "undefined" && typeof idmGeneralHotspotObjData === "object" && idmGeneralHotspotObjData?.options?.lazy) idmObserveOnce(selectedContainerEl, idmFill);
- else idmFill();
-}
-
-
-// obiekt js z przykładowymi danymi
-/**
- * Tablica konfiguracji hotspotów rekomendacji.
- *
- * @typedef {object} Hotspot
- * @property {string} id - Identyfikator ramki (required).
- * @property {string} title - Tytuł ramki.
- * @property {string} classes - Dodatkowe klasy CSS.
- * @property {object} placement - Określa, gdzie wstawić ramkę (required).
- * @property {string} placement.selector - Selektor miejsca osadzenia.
- * @property {string} placement.insert - Pozycja wstawienia względem selektora (np. "afterbegin", "beforeend").
- * @property {object} source - Dane źródłowe dla hotspotu (required).
- * @property {string} [source.hotspotType] - Typ hotspotu (np. "promotion").
- * @property {number[]} [source.productsId] - Tablica ID produktów.
- * @property {number} [source.productsMenu] - Identyfikator menu produktów.
- * @property {object} query - Dane zapytania, nadpisują source (DEV).
- * @property {string} query.string - Zapytanie w formacie GraphQL.
- * @property {Function} query.graphFn - Funkcja do pobierania danych.
- * @property {object} options - Ustawienia dla hotspotu (required).
- * @property {boolean} options.lazy - Czy wczytywać w trybie lazy.
- * @property {boolean|string} options.addToBasket - Obsługa koszyka:
- * - true = włącz
- * - false = wyłącz
- * - "range" = dodaj z zakresem
- * @property {boolean|object} options.swiper - Slider:
- * - true = aktywny
- * - false = nieaktywny
- * - object = konfiguracja Swiper
- * @property {Function} options.callbackFn - Funkcja callback która dzieje się po wywołaniu wszystkiego włącznie ze swiperem
- *
- * @type {Hotspot[]}
- */
-// const idmHotspotArr = [
-// {
-// id: "idmMainHotspot1",//!id ramki
-// title: "Nowoczesna ramka rekomendacji",//
-// classes: "abcdefg",
-// placement: {
-// selector: "#content",
-// insert: "afterbegin"
-// },
-// source: {
-// hotspotType: "protomtion",
-// productsId: [11,12],
-// productsMenu: 122,
-// },
-// query: {
-// string: `searchInput: {productsId : [589, 180, 181590,160740, 155978, 153632, 123350, 82542, 37321, 17040, 25065, 25114, 85452]}`,
-// graphFn: IDM_PRODUCTS_GQL
-// },
-// // addToBasket: true,
-// options: {
-// lazy: false,
-// addToBasket: "range",
-// swiper: true,
-// callbackFn: ()=>{console.log("test")}
-// // swiper: albo true false - albo obiekt z opcjami swipera
-// }
-// },
-// {
-// id: "idmMainHotspot2",
-// title: "Super ramka rekomendacji",
-// placement: {
-// selector: "#content",
-// insert: "beforeend"
-// },
-// source: {
-// productsMenu: 488,
-// },
-// query: {
-// string: `searchInput: {hotspot: promotion,limit: 16}`,
-// graphFn: IDM_HOTSPOTS_GQL
-// },
-// // addToBasket: true,
-// options: {
-// lazy: true,
-// addToBasket: "range",
-// swiper: true,
-// // swiper: albo true false - albo obiekt z opcjami swipera
-// }
-// }
-// ];
-
-
-// Wrzucenie na strone wszystkich ramek z obiektu js
-async function idmInsertAllObjectHotspots(hotspotArr){
- try{
- const reqArr = []
- hotspotArr.forEach(hotspotObj=>{
- reqArr.push(idmInsertHotspotObject(hotspotObj));
- });
- await Promise.all(reqArr);
- }catch(err){
- console.error(err)
- }
-}
-
-
-// idmInsertAllObjectHotspots(idmHotspotArr);
\ No newline at end of file
diff --git a/sklad/6style.css b/style.css
similarity index 100%
rename from sklad/6style.css
rename to style.css