diff --git a/README.md b/README.md
new file mode 100644
index 0000000..2561d7e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,80 @@
+# Ramki rekomendacji #
+Funkcje js składające się na customowe ramki 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.js - wstawienie styli na koniec body(do przeniesienia do css komponentu)
+
+### Użycie ###
+1. Wstawienie całego kodu do komponentu/dodatku
+2. Ustawienie defaultowych ustawień w obiekcie idmGeneralHotspotObjData
+3. Wstawienie HTML lub Obiektu js z odpowienimi danymi i wywołanie funkcji od tworzenia ramek
+
+
+#### 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);
+```
+
+
+
+
+###### Jedna ramka - HTML ######
+```
+
+
+```
+
+###### Wszystkie ramki - HTML ######
+```
+idmInsertAllHTMLHotspots();
+```
+
+
+
+
+Created by • **[IdoMods](https://idomods.pl/)** • 2025
\ No newline at end of file
diff --git a/bundle.js b/bundle.js
new file mode 100644
index 0000000..29edfb8
--- /dev/null
+++ b/bundle.js
@@ -0,0 +1,1138 @@
+///////////////////////////////////////////////
+// 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 = data[graphFn === IDM_HOTSPOTS_GQL ? "hotspots" : "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);
+}
+//////////////////////////////////////////////////////////////////////////
+// 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();
+ }
+ }catch(err){
+ console.error(idmHotspotTextObject["Wystąpił błąd z inicjalizacją. Proszę odśwież stronę"], err);
+ }
+}
+
+console.log("init")
+
+////////////////////////////////////////////////////
+// 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) 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
+ *
+ * @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,
+// // 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);
+
+// style na razie tak doawane
+document.querySelector("body").insertAdjacentHTML("beforeend", ``);
diff --git a/ramka.txt b/ramka.txt
new file mode 100644
index 0000000..f5a916b
--- /dev/null
+++ b/ramka.txt
@@ -0,0 +1,22 @@
+1. Ramka
+
+
+- ulubione? + porównywarka? (wymagają zmiany w komponencie idosella)
+- zakres cen?????????????
+- wybór rozmiaru/wersji??
+- Wybór kolorystyczny
+- AAAAA - banner na hotspocie
+
+- własne klasy
+- aplikacja do zarządania dodatkiem od obiektów hotspot
+
+Stara ramka
+- getProductXML=t
+- slick
+
+
+bramka z hotspots jeszcze nie działa bad request
+
+
+
+Get-Content 1graphQL.js,2funkcje.js,3markup.js,4init.js,5ainsertHotspotHTML.js,5binsertHotspotObject.js,6style.js | Set-Content bundle.js
\ No newline at end of file
diff --git a/sklad/1graphQL.js b/sklad/1graphQL.js
new file mode 100644
index 0000000..60f803d
--- /dev/null
+++ b/sklad/1graphQL.js
@@ -0,0 +1,207 @@
+///////////////////////////////////////////////
+// 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
new file mode 100644
index 0000000..9d7d495
--- /dev/null
+++ b/sklad/2funkcje.js
@@ -0,0 +1,296 @@
+//////////////////////////////////////////////////////////////////////////\\\\\\\\\\\\
+// 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 = data[graphFn === IDM_HOTSPOTS_GQL ? "hotspots" : "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);
+}
diff --git a/sklad/3markup.js b/sklad/3markup.js
new file mode 100644
index 0000000..98b6a42
--- /dev/null
+++ b/sklad/3markup.js
@@ -0,0 +1,147 @@
+//////////////////////////////////////////////////////////////////////////
+// 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
new file mode 100644
index 0000000..f57f5ef
--- /dev/null
+++ b/sklad/4init.js
@@ -0,0 +1,157 @@
+////////////////////////////////////////////////////
+// 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();
+ }
+ }catch(err){
+ console.error(idmHotspotTextObject["Wystąpił błąd z inicjalizacją. Proszę odśwież stronę"], err);
+ }
+}
+
+console.log("init")
\ No newline at end of file
diff --git a/sklad/5ainsertHotspotHTML.js b/sklad/5ainsertHotspotHTML.js
new file mode 100644
index 0000000..604d74d
--- /dev/null
+++ b/sklad/5ainsertHotspotHTML.js
@@ -0,0 +1,109 @@
+
+////////////////////////////////////////////////////
+// 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
new file mode 100644
index 0000000..30e5643
--- /dev/null
+++ b/sklad/5binsertHotspotObject.js
@@ -0,0 +1,165 @@
+////////////////////////////////////////////////////
+// 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
+ *
+ * @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,
+// // 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.js b/sklad/6style.js
new file mode 100644
index 0000000..4964127
--- /dev/null
+++ b/sklad/6style.js
@@ -0,0 +1,57 @@
+
+// style na razie tak doawane
+document.querySelector("body").insertAdjacentHTML("beforeend", ``);
\ No newline at end of file