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 += ` -
-
- - -
- - ${idmPrepareHotspotImgMarkup(prod)} - - ${labelHTMLMarkup} - - -
- ${prod.name} -
- ${idmPrepareHotspotPriceMarkup(prod, prodExchangedData)} -
-
- ${idmPrepareHotspotAddToBasketMarkup(prod, addToBasket)} -
`; - - return singleMarkup; -} - -// markup zdjęcia -function idmPrepareHotspotImgMarkup(prod){ - let markup = ""; - if(prod.iconSmallSecond !== undefined && prod.iconSecond !== undefined) markup +=` - - - - - ${prod.name} - `; - else if(prod?.iconSmall !== undefined) markup += ` - - - ${prod.name} - `; - else markup += `${prod.name}` - 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 -
-
-

- - aaa - -

-
-
- */ -//////////////////////////////////////////////////// -// 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 += ` +
+
+ + +
+ + ${this.markupImage(prod)} + + ${this.markupLabel(prod)} + + +
+ ${prod.name} +
+ ${this.markupPrice(prod, prodExchangedData)} +
+ ${this.markupAddToBasket(prod)} +
+
`; + + return singleMarkup; + } + + + markupImage(prod){ + let markup = ""; + if(prod.iconSmallSecond !== undefined && prod.iconSecond !== undefined) markup +=` + + + + + ${prod.name} + `; + else if(prod?.iconSmall !== undefined) markup += ` + + + ${prod.name} + `; + else markup += `${prod.name}` + 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 += ` -
-
- - -
- - ${idmPrepareHotspotImgMarkup(prod)} - - ${labelHTMLMarkup} - - -
- ${prod.name} -
- ${idmPrepareHotspotPriceMarkup(prod, prodExchangedData)} -
-
- ${idmPrepareHotspotAddToBasketMarkup(prod, addToBasket)} -
`; - - return singleMarkup; -} - -// markup zdjęcia -function idmPrepareHotspotImgMarkup(prod){ - let markup = ""; - if(prod.iconSmallSecond !== undefined && prod.iconSecond !== undefined) markup +=` - - - - - ${prod.name} - `; - else if(prod?.iconSmall !== undefined) markup += ` - - - ${prod.name} - `; - else markup += `${prod.name}` - 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 -
-
-

- - aaa - -

-
-
- */ 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