diff --git a/apilo.js b/apilo.js index e8eefe3..8cf9e80 100644 --- a/apilo.js +++ b/apilo.js @@ -4,163 +4,207 @@ const patterns = [ /^https:\/\/projectpro\.apilo\.com\/order\/order\/in-progress\/?$/, /^https:\/\/projectpro\.apilo\.com\/order\/order\/to-send\/?$/, /^https:\/\/projectpro\.apilo\.com\/order\/order\/completed\/?$/, - /^https:\/\/projectpro\.apilo\.com\/order\/order\/all\/?$/ + /^https:\/\/projectpro\.apilo\.com\/order\/order\/status\/?$/, + /^https:\/\/projectpro\.apilo\.com\/order\/order\/(news|in-progress|to-send|completed|all)\/?$/, + /^https:\/\/projectpro\.apilo\.com\/order\/order\/status\/[^\/?#]+\/?$/ ]; -if (patterns.some(pattern => pattern.test(currentUrl))) { console.log('test'); +if (patterns.some(pattern => pattern.test(currentUrl))) { + console.log('test'); waitForTableAndSetImage(); attachTableReloadListener(); } else { console.log('Nie pasuje do żadnego wzorca URL'); } +// --- 2) Czekanie na tabelę i start przetwarzania --- function waitForTableAndSetImage() { + const start = Date.now(); + const maxWait = 10000; // 10s const intervalId = setInterval(() => { - let dataTables_scrollBody = document.getElementsByClassName('dataTables_scrollBody'); - if (dataTables_scrollBody.length > 0) { - let dataTables_tbody = dataTables_scrollBody[0].getElementsByTagName('tbody')[0]; - let rows = dataTables_tbody.getElementsByTagName('tr'); - - if (rows.length > 0) { - clearInterval(intervalId); - setImageToProduct(); - } + const tbody = document.querySelector('#DataTables_Table_0 tbody'); + if (tbody && tbody.querySelector('tr')) { + clearInterval(intervalId); + setImageToProduct(); + } else if (Date.now() - start > maxWait) { + clearInterval(intervalId); } - }, 100); + }, 120); } +// --- 3) Obserwator zmian w tabeli (przeładowanie/paginacja) --- function attachTableReloadListener() { - const table = document.querySelector('.dataTables_scrollBody table'); - if (table) { - const observer = new MutationObserver((mutationsList, observer) => { - for (const mutation of mutationsList) { - if (mutation.type === 'childList') { - waitForTableAndSetImage(); - break; - } + const tbody = document.querySelector('#DataTables_Table_0 tbody'); + if (!tbody) return; + const observer = new MutationObserver(mutations => { + for (const m of mutations) { + if (m.type === 'childList') { + setImageToProduct(); + break; + } + } + }); + observer.observe(tbody, { childList: true, subtree: false }); +} + +// --- 4) Główny worker: wstawianie miniatur wg SKU --- +const imgCache = new Map(); // SKU -> imgURL + +function setImageToProduct() { + const tbody = document.querySelector('#DataTables_Table_0 tbody'); + if (!tbody) return; + + const rows = Array.from(tbody.querySelectorAll('tr')); + rows.forEach(tr => { + const cells = tr.querySelectorAll('td'); + if (cells.length < 4) return; + + // Kolumna 2 zawiera info z domeną (wklejone w tym samym TD co numer zamówienia itp.) + const domain = detectDomainFromCell(cells[1]); + if (!domain) return; + + // Kolumna 4: "Dane produktu" + const productsCell = cells[3]; + // ============ POPRAWIONY FRAGMENT ============ + + // Każdy produkt:
...
+ const productBlocks = productsCell.querySelectorAll('[data-item="all-products"] [data-item="product"]'); + + productBlocks.forEach(block => { + // nie rób drugi raz + if (block.dataset.imgInserted === '1') return; + + // Struktura: [data-item="product"] > .d-flex.mb-2 > [imgHolder DIV] + [dataHolder DIV] + const flex = + block.querySelector(':scope > .d-flex.mb-2') || + block.querySelector('.d-flex.mb-2') || + block.firstElementChild; // awaryjnie, gdyby klasy się zmieniły + + if (!flex) return; + + const imgHolder = flex.children?.[0]; //
+ const dataHolder = flex.children?.[1]; //
z nazwą, SKU, EAN, ceną + if (!imgHolder || !dataHolder) return; + + // Jeśli już jest w imgHolder, pomijamy + if (imgHolder.querySelector('img')) { + block.dataset.imgInserted = '1'; + return; + } + + // Znajdź konkretnie DIV z SKU (bez mieszania z innymi liniami) + const skuDiv = Array.from(dataHolder.querySelectorAll('div')) + .find(d => /SKU\s*:/i.test(d.textContent || '')); + + const skuMatch = (skuDiv?.textContent || '').match(/SKU\s*:\s*([A-Za-z0-9_-]+)/i); + const sku = skuMatch?.[1]?.toUpperCase(); + if (!sku) return; + + const placeImage = (imgUrl) => { + if (!imgUrl) return; + // czyścimy ikonę i wstawiamy miniaturę + imgHolder.innerHTML = ''; + const imgEl = makeImg(imgUrl); + imgHolder.appendChild(imgEl); + setupPreview(imgEl, imgUrl); + block.dataset.imgInserted = '1'; + }; + + if (imgCache.has(sku)) { + placeImage(imgCache.get(sku)); + } else { + getProductData(sku, domain) + .then(url => { + if (url) imgCache.set(sku, url); + placeImage(url); + }) + .catch(() => { }); } }); - observer.observe(table.querySelector('tbody'), { childList: true }); - } + }); } -function setImageToProduct(img = '') { - let dataTables_scrollBody = document.getElementsByClassName('dataTables_scrollBody'); - if (dataTables_scrollBody.length > 0) { - let dataTables_tbody = dataTables_scrollBody[0].getElementsByTagName('tbody')[0]; - let rows = dataTables_tbody.getElementsByTagName('tr'); - - for (let i = 0; i < rows.length; i++) { - let cells = rows[i].getElementsByTagName('td'); - - if (cells.length > 1) { - let secondCellText = cells[1].textContent.trim(); - let domain; - - if (secondCellText.includes('marianek.pl')) { - domain = 'marianek.pl'; - } else if (secondCellText.includes('pomysloweprezenty.pl')) { - domain = 'pomysloweprezenty.pl'; - } else { - continue; - } - - if (cells.length >= 5) { - let fifthCell = cells[4]; - let divsInFifthCell = fifthCell.children; - - for (let i = 0; i < divsInFifthCell.length; i++) { - let currentDiv = divsInFifthCell[i]; - let imgDiv = currentDiv.getElementsByTagName('div')[0]; - let dataDiv = currentDiv.getElementsByTagName('div')[1]; - - if (dataDiv) { - let skuText = dataDiv.innerHTML.match(/SKU:\s*([A-Za-z0-9-]+)/); - - if (skuText && skuText[1]) { - getProductData(skuText[1], domain).then(data => { - if (!data) { - console.log('Product not found:', skuText[1]); - return; - } - - console.log('Product found:', skuText[1]); - imgDiv.innerHTML = ''; - const imgElement = makeImg(data); - imgDiv.appendChild(imgElement); - - imgElement.addEventListener('mouseover', function() { - showLargeImage(data, imgElement); - }); - - imgElement.addEventListener('mouseout', function() { - hideLargeImage(); - }); - }); - } - } - } - } - } - } - } +function detectDomainFromCell(td) { + const text = (td.textContent || '').toLowerCase(); + if (text.includes('marianek.pl')) return 'marianek.pl'; + if (text.includes('pomysloweprezenty.pl')) return 'pomysloweprezenty.pl'; + return null; } -function makeImg(src = 'https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png') { +function makeImg(src) { const img = document.createElement('img'); img.src = src; - img.alt = 'image'; + img.alt = 'product'; img.className = 'img-fluid center-block'; img.style.maxWidth = '48px'; img.style.maxHeight = '48px'; - + img.style.objectFit = 'contain'; + img.style.cursor = 'zoom-in'; return img; } -function showLargeImage(src, imgElement) { - const largeImg = document.createElement('img'); - largeImg.src = src; - largeImg.style.position = 'absolute'; - largeImg.style.maxWidth = '400px'; - largeImg.style.maxHeight = '400px'; - largeImg.style.border = '1px solid #ccc'; - largeImg.style.background = '#fff'; - largeImg.style.zIndex = '1000'; - largeImg.style.top = imgElement.getBoundingClientRect().top + window.scrollY + 'px'; - largeImg.style.left = imgElement.getBoundingClientRect().left + imgElement.offsetWidth + 10 + 'px'; - largeImg.id = 'largeImagePreview'; +// --- 5) Podgląd dużego obrazka --- +function setupPreview(imgElement, src) { + let largeImg = null; - document.body.appendChild(largeImg); -} - -function hideLargeImage() { - const largeImg = document.getElementById('largeImagePreview'); - if (largeImg) { - largeImg.remove(); - } + const onMove = (ev) => { + if (!largeImg) return; + const pad = 10; + largeImg.style.top = (ev.pageY - largeImg.height / 2) + 'px'; + largeImg.style.left = (ev.pageX + pad) + 'px'; + }; + + const onOver = (ev) => { + if (largeImg) return; + largeImg = document.createElement('img'); + largeImg.id = 'largeImagePreview'; + largeImg.src = src; + largeImg.style.position = 'absolute'; + largeImg.style.maxWidth = '400px'; + largeImg.style.maxHeight = '400px'; + largeImg.style.border = '1px solid #ccc'; + largeImg.style.background = '#fff'; + largeImg.style.zIndex = '10000'; + largeImg.style.pointerEvents = 'none'; + document.body.appendChild(largeImg); + onMove(ev); + document.addEventListener('mousemove', onMove); + }; + + const onOut = () => { + if (largeImg) { + document.removeEventListener('mousemove', onMove); + largeImg.remove(); + largeImg = null; + } + }; + + imgElement.addEventListener('mouseover', onOver); + imgElement.addEventListener('mouseout', onOut); + imgElement.addEventListener('mouseleave', onOut); } +// --- 6) API: pobranie URL obrazka po SKU i domenie --- async function getProductData(sku, domain) { - let url; + let url = null; if (domain === 'marianek.pl') { - url = `https://marianek.pl/api/v1/product.php?sku=${sku}`; + url = `https://marianek.pl/api/v1/product.php?sku=${encodeURIComponent(sku)}`; } else if (domain === 'pomysloweprezenty.pl') { - url = `https://pomysloweprezenty.pl/api/v1/product.php?sku=${sku}`; + url = `https://pomysloweprezenty.pl/api/v1/product.php?sku=${encodeURIComponent(sku)}`; } else { - console.error('Unsupported domain:', domain); return null; } try { - const response = await fetch(url); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); + const resp = await fetch(url, { credentials: 'omit' }); + if (!resp.ok) { + throw new Error(`HTTP error! status: ${resp.status}`); } - - const data = await response.json(); console.log(data); - return data.img; - } catch (error) { - console.error('Error fetching product data: ' + url, error); + const data = await resp.json(); + // zakładam, że API zwraca { img: "https://..." } + return data?.img || null; + } catch (err) { + console.error('Error fetching product data:', url, err); return null; } } @@ -223,7 +267,7 @@ async function handleShipment(method, dropoffPoint = null) { if (dropoffPoint) { await setDropoffPoint(dropoffPoint); } - await setContent( method ); + await setContent(method); await setParcelWeight('1'); await submitShipment(); } catch (error) { @@ -252,8 +296,8 @@ function retryUntilSuccess(fn, interval = 1000, retries = 10) { }); } -function setContent( method ) { - if ( method == 'D2D.apaczka' || method == 'D2P.apaczka' || method == 'P2P.apaczka' || method == 'P2P.orlen' ) { +function setContent(method) { + if (method == 'D2D.apaczka' || method == 'D2P.apaczka' || method == 'P2P.apaczka' || method == 'P2P.orlen') { return retryUntilSuccess(() => { return new Promise((resolve, reject) => { const selectElement = document.getElementById('warehousebundle_shipment_preferences_content'); @@ -271,7 +315,7 @@ function selectPackageType(method) { return retryUntilSuccess(() => { return new Promise((resolve, reject) => { - if ( method == 'P2P.orlen' ) { + if (method == 'P2P.orlen') { //warehousebundle_shipment_carrierAccount const selectElement = document.getElementById('warehousebundle_shipment_carrierAccount'); @@ -300,7 +344,7 @@ function selectPackageType(method) { }, 1000); }, 1000); - } else if ( method == 'D2P.apaczka' || method == 'P2P.apaczka' ) { + } else if (method == 'D2P.apaczka' || method == 'P2P.apaczka') { //warehousebundle_shipment_carrierAccount const selectElement = document.getElementById('warehousebundle_shipment_carrierAccount'); @@ -330,7 +374,7 @@ function selectPackageType(method) { }, 1000); - } else if ( method == 'D2D.apaczka' ) { + } else if (method == 'D2D.apaczka') { const selectElement = document.getElementById('warehousebundle_shipment_carrierAccount'); selectElement.value = 17; @@ -375,7 +419,7 @@ function selectPackageType(method) { } function setShipmentMethod(method) { - if ( method == 'D2D.apaczka' || method == 'D2P.apaczka' || method == 'P2P.apaczka' || method == 'P2P.orlen' ) { + if (method == 'D2D.apaczka' || method == 'D2P.apaczka' || method == 'P2P.apaczka' || method == 'P2P.orlen') { return new Promise((resolve, reject) => { resolve(); });