Greasy Fork is available in English.
Pakiet usprawnien do Allegro.pl
// ==UserScript==
// @name Allegro+
// @namespace http://tampermonkey.net/
// @version 1.5
// @description Pakiet usprawnien do Allegro.pl
// @author Paweł Kaczmarek
// @match https://allegro.pl/uzytkownik/*/oceny*
// @match https://allegro.pl/my-assortment*
// @match https://allegro.pl/listing?string=*
// @match https://allegro.pl/produkt/*
// @match https://allegro.pl/kategoria/*
// @match https://allegro.pl/oferty-produktu/*
// @match https://allegro.pl/uzytkownik/*
// @match https://allegro.pl/oferta/*
// @match https://salescenter.allegro.com/campaigns*
// @match https://salescenter.allegro.com/costs-summary*
// @grant GM_download
// @grant GM_xmlhttpRequest
// @grant GM_setClipboard
// @require https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js
// ==/UserScript==
// --- PANEL USTAWIEŃ ---
(function() {
const vSprintFeatures = [
{ key: 'oceny', label: 'Raport z ocen' },
{ key: 'filtr', label: 'Lepszy filtr' },
{ key: 'info', label: 'vSprint info' },
{ key: 'img', label: 'vSprint Image Downloader' },
{ key: 'kampanie', label: 'Asystent Kampanii' },
{ key: 'kampFiltr', label: 'Filtr ofert w kampaniach' },
{ key: 'kopiujId', label: 'Kopiuj ID' },
{ key: 'edytuj', label: 'Edytuj' }
];
function getVSprintSettings() {
let settings = {};
try { settings = JSON.parse(localStorage.getItem('vSprintPlusSettings') || '{}'); } catch (e) {}
vSprintFeatures.forEach(f => { if (typeof settings[f.key] === 'undefined') settings[f.key] = true; });
return settings;
}
window.vSprintPlusSettings = getVSprintSettings();
})();
// --- OCENY ANALIZATOR ---
(function() {
'use strict';
if (window.vSprintPlusSettings && window.vSprintPlusSettings.oceny === false) return;
// Uruchamiaj tylko na stronie ocen użytkownika
if (!window.location.href.match(/^https:\/\/allegro\.pl\/uzytkownik\/[^\/]+\/oceny/)) {
return; // Przerywamy wykonywanie tego bloku
}
let sellerId = null;
let isSecondPageVisited = false;
let ratingsData = [];
let fetchDetected = false;
// Funkcja do wyciągnięcia ID sprzedawcy z URL
function extractSellerId() {
const match = window.location.pathname.match(/\/uzytkownik\/([^\/]+)\/oceny/);
if (match) {
// Tutaj będziemy musieli pobrać ID sprzedawcy z API
// Na razie ustawiamy placeholder
return '92089972'; // To będzie musiało być pobrane dynamicznie
}
return null;
}
// Funkcja do tworzenia przycisku Raport
function createReportButton() {
if (document.getElementById('allegro-report-btn')) return;
const btn = document.createElement('button');
btn.id = 'allegro-report-btn';
btn.textContent = 'Raport';
btn.style.position = 'fixed';
btn.style.top = '10px';
btn.style.right = '20px';
btn.style.zIndex = '99999';
btn.style.background = '#23272e';
btn.style.color = '#fff';
btn.style.border = '1.5px solid #ff5a00';
btn.style.borderRadius = '8px';
btn.style.padding = '10px 20px';
btn.style.fontSize = '16px';
btn.style.cursor = 'pointer';
btn.style.boxShadow = '0 2px 8px rgba(0,0,0,0.10)';
btn.style.fontWeight = 'bold';
btn.style.transition = 'background 0.2s, color 0.2s';
btn.onmouseenter = function() {
btn.style.background = '#ff5a00';
btn.style.color = '#fff';
};
btn.onmouseleave = function() {
btn.style.background = '#23272e';
btn.style.color = '#fff';
};
btn.onclick = function() {
if (!fetchDetected || !sellerId) {
alert('Przejdź na kolejną stronę aby móc wygenerować raport');
return;
}
showReportModal();
};
document.body.appendChild(btn);
}
// Funkcja do wyświetlania modala raportu
function showReportModal() {
if (document.getElementById('allegro-report-modal')) return;
const modal = document.createElement('div');
modal.id = 'allegro-report-modal';
modal.style.position = 'fixed';
modal.style.top = '50%';
modal.style.left = '50%';
modal.style.transform = 'translate(-50%, -50%)';
modal.style.background = '#fff';
modal.style.border = '1px solid #ddd';
modal.style.borderRadius = '10px';
modal.style.boxShadow = '0 2px 16px rgba(0,0,0,0.18)';
modal.style.padding = '24px';
modal.style.zIndex = '100000';
modal.style.minWidth = '320px';
// Nagłówek
const title = document.createElement('div');
title.textContent = 'Raport Produktów z Ocen';
title.style.fontWeight = '600';
title.style.fontSize = '20px';
title.style.marginBottom = '25px';
title.style.color = '#2c3e50';
title.style.textAlign = 'center';
title.style.borderBottom = '2px solid #f8f9fa';
title.style.paddingBottom = '15px';
modal.appendChild(title);
// Pole do wpisania liczby stron
const label = document.createElement('label');
label.textContent = 'Liczba stron do przetworzenia (max 20):';
label.style.display = 'block';
label.style.marginBottom = '10px';
label.style.color = '#000';
modal.appendChild(label);
const input = document.createElement('input');
input.type = 'number';
input.min = '1';
input.max = '20';
input.value = '5';
input.style.width = '100%';
input.style.padding = '12px 16px';
input.style.border = '2px solid #e1e5e9';
input.style.borderRadius = '8px';
input.style.marginBottom = '20px';
input.style.fontSize = '16px';
input.style.color = '#333';
input.style.backgroundColor = '#fff';
input.style.boxSizing = 'border-box';
input.style.transition = 'border-color 0.2s ease, box-shadow 0.2s ease';
input.style.outline = 'none';
// Hover i focus efekty
input.addEventListener('focus', function() {
this.style.borderColor = '#ff5a00';
this.style.boxShadow = '0 0 0 3px rgba(255, 90, 0, 0.1)';
});
input.addEventListener('blur', function() {
this.style.borderColor = '#e1e5e9';
this.style.boxShadow = 'none';
});
modal.appendChild(input);
// Przycisk pobierz
const downloadBtn = document.createElement('button');
downloadBtn.textContent = 'Pobierz Raport';
downloadBtn.style.background = '#ff5a00';
downloadBtn.style.color = '#fff';
downloadBtn.style.border = 'none';
downloadBtn.style.borderRadius = '6px';
downloadBtn.style.padding = '10px 20px';
downloadBtn.style.fontSize = '16px';
downloadBtn.style.cursor = 'pointer';
downloadBtn.style.marginRight = '10px';
downloadBtn.onclick = function() {
const pages = parseInt(input.value);
if (pages < 1 || pages > 20) {
alert('Liczba stron musi być między 1 a 20');
return;
}
generateReport(pages);
};
modal.appendChild(downloadBtn);
// Przycisk zamknij
const closeBtn = document.createElement('button');
closeBtn.textContent = 'Zamknij';
closeBtn.style.background = '#ccc';
closeBtn.style.color = '#000';
closeBtn.style.border = 'none';
closeBtn.style.borderRadius = '6px';
closeBtn.style.padding = '10px 20px';
closeBtn.style.fontSize = '16px';
closeBtn.style.cursor = 'pointer';
closeBtn.onclick = function() {
modal.remove();
};
modal.appendChild(closeBtn);
document.body.appendChild(modal);
}
// Funkcja do generowania raportu Excel
async function generateReport(pages) {
if (!sellerId) {
alert('Nie udało się pobrać ID sprzedawcy');
return;
}
const modal = document.getElementById('allegro-report-modal');
const downloadBtn = modal.querySelector('button');
downloadBtn.textContent = 'Pobieranie...';
downloadBtn.disabled = true;
// Dodaj progress bar
const progressContainer = document.createElement('div');
progressContainer.style.marginTop = '15px';
progressContainer.style.marginBottom = '15px';
const progressText = document.createElement('div');
progressText.textContent = 'Pobieranie danych... (0/' + pages + ')';
progressText.style.textAlign = 'center';
progressText.style.marginBottom = '10px';
progressText.style.color = '#666';
const progressBar = document.createElement('div');
progressBar.style.width = '100%';
progressBar.style.height = '20px';
progressBar.style.backgroundColor = '#f0f0f0';
progressBar.style.borderRadius = '10px';
progressBar.style.overflow = 'hidden';
const progressFill = document.createElement('div');
progressFill.style.width = '0%';
progressFill.style.height = '100%';
progressFill.style.backgroundColor = '#ff5a00';
progressFill.style.transition = 'width 0.3s ease';
progressBar.appendChild(progressFill);
progressContainer.appendChild(progressText);
progressContainer.appendChild(progressBar);
modal.appendChild(progressContainer);
try {
const allRatings = [];
// Pobieramy dane ze wszystkich stron z opóźnieniami
for (let page = 1; page <= pages; page++) {
// Aktualizuj progress
progressText.textContent = `Pobieranie danych... (${page}/${pages})`;
progressFill.style.width = `${(page / pages) * 100}%`;
const response = await fetch(`https://edge.allegro.pl/ratings-api/sellers/${sellerId}/ratings?page=${page}`, {
headers: {
"accept": "application/vnd.allegro.public.v5+json",
"accept-language": "pl-PL",
"content-type": "application/vnd.allegro.public.v5+json",
"priority": "u=1, i",
"sec-ch-ua": "\"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"138\", \"Google Chrome\";v=\"138\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site"
},
referrer: "https://allegro.pl/",
method: "GET",
mode: "cors",
credentials: "include"
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
allRatings.push(...data.content);
console.log(`✅ Pobrano stronę ${page}/${pages} (${data.content.length} ocen)`);
// Dodaj losowe opóźnienie między requestami (1-4 sekundy)
if (page < pages) {
const delay = Math.random() * 3000 + 1000; // 1000-4000ms
console.log(`⏳ Czekam ${Math.round(delay)}ms przed następnym requestem...`);
progressText.textContent = `Czekam... (${page}/${pages})`;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
// Analizujemy dane
progressText.textContent = 'Analizowanie danych...';
progressFill.style.width = '100%';
const offerStats = {};
allRatings.forEach(rating => {
if (rating.offers && rating.offers.length > 1) {
// Sprawdzamy tylko oferty które nie są pierwsze (indeks > 0)
for (let i = 1; i < rating.offers.length; i++) {
const offer = rating.offers[i];
const key = `${offer.id}_${offer.title}`;
if (!offerStats[key]) {
offerStats[key] = {
id: offer.id,
title: offer.title,
count: 0
};
}
offerStats[key].count++;
}
}
});
// Generujemy dane do Excel
progressText.textContent = 'Generowanie raportu...';
const excelData = [
['ID Oferty', 'Nazwa Oferty', 'Ilość Dobrań']
];
Object.values(offerStats)
.sort((a, b) => b.count - a.count)
.forEach(offer => {
excelData.push([offer.id, offer.title, offer.count]);
});
// Tworzymy workbook i worksheet
const wb = XLSX.utils.book_new();
const ws = XLSX.utils.aoa_to_sheet(excelData);
// Dodajemy worksheet do workbook
XLSX.utils.book_append_sheet(wb, ws, 'Raport Oceny');
// Generujemy plik Excel
const excelBuffer = XLSX.write(wb, { bookType: 'xlsx', type: 'array' });
const blob = new Blob([excelBuffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
// Pobieramy plik
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = `raport_oceny_${new Date().toISOString().split('T')[0]}.xlsx`;
link.click();
progressText.textContent = 'Raport wygenerowany!';
downloadBtn.textContent = 'Pobierz Raport';
downloadBtn.disabled = false;
console.log(`✅ Raport wygenerowany: ${Object.keys(offerStats).length} produktów`);
} catch (error) {
console.error('Błąd podczas generowania raportu:', error);
alert('Wystąpił błąd podczas generowania raportu: ' + error.message);
downloadBtn.textContent = 'Pobierz Raport';
downloadBtn.disabled = false;
}
}
// Funkcja do sprawdzania czy jesteśmy na drugiej stronie
function checkIfSecondPage() {
const urlParams = new URLSearchParams(window.location.search);
const page = urlParams.get('page');
if (page && parseInt(page) >= 2) {
isSecondPageVisited = true;
console.log('Wykryto drugą stronę, czekam na fetch...');
}
}
// Funkcja do przechwytywania fetch requestów
function interceptFetchRequests() {
const originalFetch = window.fetch;
window.fetch = function(...args) {
const url = args[0];
console.log('=== FETCH REQUEST WYKRYTY ===');
console.log('URL:', url);
console.log('Typ URL:', typeof url);
// Sprawdzamy różne wzorce URL
const patterns = [
/edge\.allegro\.pl\/ratings-api\/sellers\/(\d+)\/ratings/,
/ratings-api\/sellers\/(\d+)\/ratings/,
/sellers\/(\d+)\/ratings/
];
let match = null;
for (const pattern of patterns) {
if (typeof url === 'string') {
match = url.match(pattern);
if (match) {
console.log('Znaleziono match z patternem:', pattern);
break;
}
}
}
if (match) {
const newSellerId = match[1];
console.log('Nowe ID sprzedawcy:', newSellerId);
console.log('Aktualne ID sprzedawcy:', sellerId);
if (!sellerId || sellerId !== newSellerId) {
sellerId = newSellerId;
fetchDetected = true;
console.log('✅ ID sprzedawcy zaktualizowane:', sellerId);
// Zmień kolor przycisku
const btn = document.getElementById('allegro-report-btn');
if (btn) {
btn.style.background = '#28a745';
btn.textContent = 'Raport (Pobierz)';
console.log('✅ Przycisk zmieniony na zielony');
} else {
console.log('❌ Przycisk nie został znaleziony');
}
}
} else {
console.log('❌ Nie znaleziono ID sprzedawcy w URL:', url);
}
return originalFetch.apply(this, args);
};
}
// Funkcja do przechwytywania XMLHttpRequest
function interceptXHRRequests() {
const originalOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url, ...args) {
console.log('=== XHR REQUEST WYKRYTY ===');
console.log('URL:', url);
console.log('Method:', method);
// Sprawdzamy różne wzorce URL
const patterns = [
/edge\.allegro\.pl\/ratings-api\/sellers\/(\d+)\/ratings/,
/ratings-api\/sellers\/(\d+)\/ratings/,
/sellers\/(\d+)\/ratings/
];
let match = null;
for (const pattern of patterns) {
if (typeof url === 'string') {
match = url.match(pattern);
if (match) {
console.log('Znaleziono match z patternem (XHR):', pattern);
break;
}
}
}
if (match) {
const newSellerId = match[1];
console.log('Nowe ID sprzedawcy (XHR):', newSellerId);
if (!sellerId || sellerId !== newSellerId) {
sellerId = newSellerId;
fetchDetected = true;
console.log('✅ ID sprzedawcy zaktualizowane (XHR):', sellerId);
// Zmień kolor przycisku
const btn = document.getElementById('allegro-report-btn');
if (btn) {
btn.style.background = '#28a745';
btn.textContent = 'Raport (Pobierz)';
console.log('✅ Przycisk zmieniony na zielony (XHR)');
}
}
}
return originalOpen.apply(this, [method, url, ...args]);
};
}
// Funkcja do sprawdzania aktywnych requestów w Network tab
function checkNetworkRequests() {
console.log('🔍 Sprawdzam aktywnych requestów...');
// Sprawdź czy są jakieś requesty do ratings API
const performanceEntries = performance.getEntriesByType('resource');
const ratingsRequests = performanceEntries.filter(entry =>
entry.name.includes('ratings-api/sellers/') &&
entry.name.includes('/ratings')
);
console.log('Znalezione requesty ratings:', ratingsRequests);
if (ratingsRequests.length > 0) {
const latestRequest = ratingsRequests[ratingsRequests.length - 1];
const match = latestRequest.name.match(/sellers\/(\d+)\/ratings/);
if (match) {
const newSellerId = match[1];
console.log('✅ Znaleziono ID sprzedawcy w Network:', newSellerId);
if (!sellerId || sellerId !== newSellerId) {
sellerId = newSellerId;
fetchDetected = true;
console.log('✅ ID sprzedawcy zaktualizowane z Network:', sellerId);
// Zmień kolor przycisku
const btn = document.getElementById('allegro-report-btn');
if (btn) {
btn.style.background = '#28a745';
btn.textContent = 'Raport (Pobierz)';
console.log('✅ Przycisk zmieniony na zielony z Network');
}
}
}
}
}
// Funkcja do nasłuchiwania zmian URL (historia przeglądarki)
function listenForURLChanges() {
let currentURL = window.location.href;
// Sprawdzaj URL co 500ms
setInterval(() => {
if (window.location.href !== currentURL) {
currentURL = window.location.href;
console.log('🔄 URL zmieniony na:', currentURL);
// Sprawdź czy to druga strona
const urlParams = new URLSearchParams(window.location.search);
const page = urlParams.get('page');
if (page && parseInt(page) >= 2) {
console.log('📄 Wykryto przejście na stronę:', page);
// Sprawdź Network requesty po 500ms
setTimeout(() => {
checkNetworkRequests();
}, 500);
// Sprawdź ponownie po 1 sekundzie
setTimeout(() => {
if (!fetchDetected) {
console.log('⚠️ Fetch nie został wykryty po 1 sekundzie');
console.log('Aktualny sellerId:', sellerId);
console.log('fetchDetected:', fetchDetected);
checkNetworkRequests();
}
}, 1000);
}
}
}, 500);
}
// Inicjalizacja
function init() {
sellerId = extractSellerId();
createReportButton();
checkIfSecondPage();
interceptFetchRequests();
interceptXHRRequests();
listenForURLChanges();
console.log('🚀 Skrypt Allegro Oceny Analizator zainicjalizowany');
console.log('📊 Aktualny sellerId:', sellerId);
console.log('👀 Czekam na fetch requesty...');
}
// Uruchomienie skryptu
init();
})();
// --- ALLEGRO FILTR ---
(function() {
'use strict';
if (window.vSprintPlusSettings && window.vSprintPlusSettings.filtr === false) return;
// Pomocnicza funkcja do debounce
function debounce(func, wait) {
let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
// Tworzy panel filtrów nad tabelą
function createFilterPanel(table) {
// Sprawdź czy panel już istnieje
if (document.getElementById('allegro-id-filter-panel') || document.getElementById('allegro-vsearch-btn')) return;
// --- Przycisk vSearch ---
const vSearchBtn = document.createElement('button');
vSearchBtn.id = 'allegro-vsearch-btn';
vSearchBtn.innerText = 'vSearch';
vSearchBtn.style.position = 'absolute';
vSearchBtn.style.top = '10px';
vSearchBtn.style.right = '30px';
vSearchBtn.style.zIndex = '10001';
vSearchBtn.style.background = '#23272e';
vSearchBtn.style.color = '#fff';
vSearchBtn.style.border = 'none';
vSearchBtn.style.padding = '8px 18px';
vSearchBtn.style.fontSize = '16px';
vSearchBtn.style.borderRadius = '8px';
vSearchBtn.style.boxShadow = '0 2px 8px rgba(0,0,0,0.18)';
vSearchBtn.style.cursor = 'pointer';
vSearchBtn.style.letterSpacing = '1px';
vSearchBtn.style.fontWeight = 'bold';
vSearchBtn.style.transition = 'background 0.2s';
vSearchBtn.onmouseenter = () => vSearchBtn.style.background = '#ff5a00';
vSearchBtn.onmouseleave = () => vSearchBtn.style.background = '#23272e';
vSearchBtn.onclick = function() {
vSearchBtn.style.display = 'none';
panel.style.display = 'flex';
setTimeout(() => input.focus(), 100);
};
// Wstaw przycisk nad tabelą (do najbliższego kontenera)
table.parentElement.style.position = 'relative';
table.parentElement.insertBefore(vSearchBtn, table);
// --- Panel filtrów ---
const panel = document.createElement('div');
panel.id = 'allegro-id-filter-panel';
panel.style.background = '#23272e';
panel.style.border = '1px solid #ff5a00';
panel.style.padding = '18px 24px';
panel.style.margin = '16px 0';
panel.style.marginTop = '60px';
panel.style.borderRadius = '12px';
panel.style.display = 'none';
panel.style.alignItems = 'center';
panel.style.gap = '16px';
panel.style.boxShadow = '0 4px 24px rgba(0,0,0,0.18)';
panel.style.position = 'relative';
panel.style.zIndex = '10000';
// Panel szerszy (usuwamy maxWidth)
panel.style.removeProperty('max-width');
panel.style.minWidth = '340px';
// Zamykacz X
const closeBtn = document.createElement('button');
closeBtn.innerText = '✕';
closeBtn.title = 'Zamknij';
closeBtn.style.position = 'absolute';
closeBtn.style.top = '2px'; // wyżej
closeBtn.style.right = '2px'; // bardziej w prawo
closeBtn.style.background = 'transparent';
closeBtn.style.color = '#fff';
closeBtn.style.border = 'none';
closeBtn.style.fontSize = '22px';
closeBtn.style.cursor = 'pointer';
closeBtn.style.fontWeight = 'bold';
closeBtn.style.padding = '4px 8px';
closeBtn.onmouseenter = () => closeBtn.style.color = '#ff5a00';
closeBtn.onmouseleave = () => closeBtn.style.color = '#fff';
closeBtn.onclick = function() {
panel.style.display = 'none';
vSearchBtn.style.display = 'inline-block';
};
panel.appendChild(closeBtn);
const label = document.createElement('label');
label.innerText = 'Filtruj ID/SKU produktu:';
label.style.fontWeight = 'bold';
label.style.marginRight = '8px';
label.style.color = '#fff';
label.htmlFor = 'allegro-id-filter-input';
const input = document.createElement('input');
input.type = 'text';
input.id = 'allegro-id-filter-input';
input.placeholder = 'np. 1772318739, SKU1234';
input.title = 'np. 1772318739, SKU1234';
input.style.padding = '10px 16px';
input.style.fontSize = '18px';
input.style.border = '1px solid #ff5a00';
input.style.borderRadius = '6px';
input.style.flex = '1';
input.style.background = '#181a1f';
input.style.color = '#fff';
input.style.outline = 'none';
input.onfocus = () => input.style.border = '1.5px solid #ff5a00';
input.onblur = () => input.style.border = '1px solid #ff5a00';
const button = document.createElement('button');
button.innerText = 'Filtruj';
button.style.background = '#ff5a00';
button.style.color = '#fff';
button.style.border = 'none';
button.style.padding = '10px 28px';
button.style.fontSize = '14px';
button.style.borderRadius = '6px';
button.style.cursor = 'pointer';
button.style.marginLeft = '8px';
button.style.fontWeight = 'bold';
button.style.boxShadow = '0 2px 8px rgba(0,0,0,0.10)';
button.onmouseenter = () => button.style.background = '#23272e';
button.onmouseleave = () => button.style.background = '#ff5a00';
const reset = document.createElement('button');
reset.innerText = 'Pokaż wszystko';
reset.style.background = '#444';
reset.style.color = '#fff';
reset.style.border = 'none';
reset.style.padding = '10px 18px';
reset.style.fontSize = '14px';
reset.style.borderRadius = '6px';
reset.style.cursor = 'pointer';
reset.style.marginLeft = '8px';
reset.style.fontWeight = 'bold';
reset.onmouseenter = () => reset.style.background = '#ff5a00';
reset.onmouseleave = () => reset.style.background = '#444';
panel.appendChild(label);
panel.appendChild(input);
panel.appendChild(button);
panel.appendChild(reset);
// Wstaw panel nad tabelą
table.parentElement.insertBefore(panel, table);
// --- Checkbox 'zaznacz wyszukane' ---
const selectAllDiv = document.createElement('div');
selectAllDiv.style.display = 'flex';
selectAllDiv.style.alignItems = 'center';
selectAllDiv.style.gap = '8px';
selectAllDiv.style.margin = '0 0 8px 0';
selectAllDiv.style.fontSize = '15px';
selectAllDiv.style.fontWeight = 'bold';
selectAllDiv.style.color = '#fff';
selectAllDiv.style.background = 'transparent';
selectAllDiv.style.userSelect = 'none';
const selectAllCheckbox = document.createElement('input');
selectAllCheckbox.type = 'checkbox';
selectAllCheckbox.id = 'allegro-select-all-visible';
selectAllCheckbox.style.width = '18px';
selectAllCheckbox.style.height = '18px';
selectAllCheckbox.style.cursor = 'pointer';
selectAllCheckbox.style.accentColor = '#ff5a00';
selectAllCheckbox.style.margin = '0';
const selectAllLabel = document.createElement('label');
selectAllLabel.innerText = 'zaznacz wyszukane';
selectAllLabel.htmlFor = 'allegro-select-all-visible';
selectAllLabel.style.cursor = 'pointer';
selectAllDiv.appendChild(selectAllCheckbox);
selectAllDiv.appendChild(selectAllLabel);
// Wstawiamy pod panelem, nad tabelą
table.parentElement.insertBefore(selectAllDiv, table);
// Funkcja do zaznaczania/odznaczania widocznych checkboxów
function setVisibleCheckboxes(checked) {
const tbody = table.querySelector('tbody');
if (!tbody) return;
Array.from(tbody.querySelectorAll('tr')).forEach(tr => {
if (tr.style.display === 'none') return;
// Szukamy input[type=checkbox] w pierwszej kolumnie
const td = tr.querySelector('td');
if (!td) return;
const input = td.querySelector('input[type="checkbox"]');
if (input && !input.disabled) {
if (input.checked !== checked) {
input.click(); // wywołaj kliknięcie tylko jeśli stan się zmienia
}
}
});
}
selectAllCheckbox.addEventListener('change', function() {
setVisibleCheckboxes(this.checked);
});
// Resetuj checkbox po zmianie filtra (odznacz, jeśli filtr się zmienia)
function resetSelectAllCheckbox() {
selectAllCheckbox.checked = false;
}
// Obsługa filtrowania
function filterRows() {
const val = input.value.trim();
if (!val) {
showAllRows(table);
resetSelectAllCheckbox();
return;
}
// Zamień wszystkie białe znaki na przecinki, rozdziel po przecinku
const filters = val.replace(/\s+/g, ',').split(',').map(x => x.trim()).filter(Boolean);
filterTableByIdsOrSku(table, filters);
resetSelectAllCheckbox();
}
button.onclick = filterRows;
input.addEventListener('keydown', function (e) {
if (e.key === 'Enter') filterRows();
});
input.addEventListener('input', debounce(filterRows, 500));
reset.onclick = function () {
input.value = '';
showAllRows(table);
};
}
// Pokazuje wszystkie wiersze
function showAllRows(table) {
const tbody = table.querySelector('tbody');
if (!tbody) return;
Array.from(tbody.querySelectorAll('tr')).forEach(tr => {
tr.style.display = '';
});
}
// Filtruje wiersze tabeli po ID
function filterTableByIds(table, ids) {
const tbody = table.querySelector('tbody');
if (!tbody) return;
const idSet = new Set(ids);
Array.from(tbody.querySelectorAll('tr')).forEach(tr => {
// Ignoruj puste wiersze (np. z paddingiem na dole)
const hasDataCy = tr.hasAttribute('data-cy');
const idCell = tr.querySelector('span._b4f97_bk6uO');
if (!hasDataCy && !idCell) {
// Nie zmieniaj stylu, zostaw wiersz w spokoju
return;
}
// 1. Spróbuj znaleźć ID w <span class="_b4f97_bk6uO">
let id = idCell ? idCell.innerText.trim() : null;
// 2. Jeśli nie ma, spróbuj z data-cy na <tr>
if (!id && hasDataCy) {
id = tr.getAttribute('data-cy');
}
// 3. Jeśli nie ma ID, ukryj wiersz
if (!id) {
tr.style.display = 'none';
return;
}
// 4. Ukryj lub pokaż wiersz w zależności od tego, czy ID jest na liście
if (idSet.has(id)) {
tr.style.display = '';
} else {
tr.style.display = 'none';
}
});
}
// Nowa funkcja: filtruje po ID lub sygnaturze (SKU, częściowe dopasowanie)
function filterTableByIdsOrSku(table, filters) {
const tbody = table.querySelector('tbody');
if (!tbody) return;
Array.from(tbody.querySelectorAll('tr')).forEach(tr => {
// Ignoruj puste wiersze (np. z paddingiem na dole)
const hasDataCy = tr.hasAttribute('data-cy');
const idCells = tr.querySelectorAll('span._b4f97_bk6uO');
if (!hasDataCy && idCells.length === 0) {
// Nie zmieniaj stylu, zostaw wiersz w spokoju
return;
}
// 1. ID produktu
let id = null;
if (hasDataCy) id = tr.getAttribute('data-cy');
// 2. Sygnatura (SKU) – drugi span._b4f97_bk6uO w pierwszej kolumnie
let sku = null;
if (idCells.length > 1) {
sku = idCells[1].innerText.trim();
} else if (idCells.length === 1) {
sku = idCells[0].innerText.trim();
}
// 3. Sprawdź czy którykolwiek filtr pasuje do ID lub SKU (częściowe dopasowanie do SKU)
let visible = false;
for (const f of filters) {
if ((id && id === f) || (sku && sku.toLowerCase().includes(f.toLowerCase()))) {
visible = true;
break;
}
}
tr.style.display = visible ? '' : 'none';
});
}
// Szuka tabeli ofert i wstawia panel filtrów
function tryInjectPanel() {
const table = document.querySelector('table[aria-label="lista ofert"]');
if (table) {
createFilterPanel(table);
// Dodajemy MutationObserver na <tbody> do dynamicznego filtrowania
const tbody = table.querySelector('tbody');
if (tbody && !tbody._allegroFilterObserver) {
const observer = new MutationObserver(() => {
// Pobierz aktualne ID z inputa
const input = document.getElementById('allegro-id-filter-input');
if (!input) return;
const val = input.value.trim();
if (!val) {
showAllRows(table);
return;
}
const filters = val.replace(/\s+/g, ',').split(',').map(x => x.trim()).filter(Boolean);
filterTableByIdsOrSku(table, filters);
});
observer.observe(tbody, { childList: true, subtree: true });
tbody._allegroFilterObserver = observer;
}
}
}
// Dla dynamicznego ładowania
const observer = new MutationObserver(() => {
tryInjectPanel();
});
observer.observe(document.body, { childList: true, subtree: true });
// Na wszelki wypadek po załadowaniu
window.addEventListener('load', () => {
setTimeout(tryInjectPanel, 1000);
});
})();
// --- ALLEGRO INFO+ ---
(function() {
'use strict';
if (window.vSprintPlusSettings && window.vSprintPlusSettings.info === false) return;
function addStyles() {
var styleContent = `
.date-container {
margin-bottom: 20px;
}
.date-container label {
display: block;
margin-bottom: 5px;
}
.date-container input {
width: 95%;
padding: 12px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 16px;
}
#fetch-data-btn, #showEntriesBtn, .show-successful-stats, .show-total-stats, .product-btn {
padding: 10px 15px;
color: #fff;
border: none;
border-radius: 5px;
cursor: pointer;
text-decoration: none;
}
#fetch-data-btn{
margin-left:5px;
}
.date-button {
padding: 10px 15px;
color: #fff;
border: none;
border-radius: 5px;
cursor: pointer;
margin-left: 5px;
margin-top: 5px;
}
.date-button:hover{
background-color: #0056b3;}
.DataButton-container{
padding-bottom: 10px;
}
.prom-efficiency {
margin: 40px 0px 20px 0px;
font-family: Arial, sans-serif;
}
.prom-efficiency table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
.prom-efficiency thead {
background-color: #878c91;
color: white;
}
.prom-efficiency th,
.prom-efficiency td {
padding: 12px 15px;
text-align: left;
border: 1px solid #ddd;
}
.prom-efficiency tr:nth-child(even) {
background-color: #f2f2f2;
}
.prom-efficiency h3 {
margin-bottom: 10px;
}
.prom-efficiency td:first-child {
font-weight: bold;
}
#statystyka-content h3{
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
color: white; /* Kolor tekstu */
padding: 10px; /* Wypełnienie wewnętrzne */
border-top-left-radius: 8px; /* Zaokrąglenie lewego górnego rogu */
border-top-right-radius: 8px; /* Zaokrąglenie prawego górnego rogu */
margin: -15px -15px 15px -15px; /* Negatywne marginesy, aby nagłówek wkomponował się w ramkę */
font-size: 0.9em; /* Zwiększenie rozmiaru czcionki */
font-weight: bold; /* Ustawienie czcionki na pogrubioną */
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5); /* Cień tekstu */
}
#statystyka-content button, .product-btn {
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
}
#statystyka-content .date-button {
background-color: grey !important;
background: none;
}
.total-stats, .successful-stats{
margin-top:40px;
}
.section-title {
margin-top: 0;
font-size: 1.2em;
position: relative; /* Umożliwia pozycjonowanie pseudo-elementu */
}
.section-title::after {
content: ""; /* Pusty zawartość, tworzy linię */
display: block; /* Umożliwia ustawienie wysokości */
width: 100%; /* Długość linii */
height: 2px; /* Grubość linii */
background: #ccc; /* Kolor linii */
margin-top: 5px; /* Odstęp od nagłówka */
}
`;
var style = document.createElement('style');
style.textContent = styleContent;
document.head.appendChild(style);
}
// Wywołanie funkcji do dodania stylów
addStyles();
// Funkcja do dodania przycisku do artykułów
function addStatystykaButton() {
// Pobieramy wszystkie artykuły w możliwych kontenerach
var articles = document.querySelectorAll("div[data-role='rightItems'] article, .sponsored-article-selector article, li article");
articles.forEach(function(article) {
// Sprawdzamy, czy przyciski już istnieją, aby nie dodawać ich wielokrotnie
if (article.querySelector("button.statystyka-btn")) return;
// Sprawdź czy to oferta sponsorowana
var isSponsored = article.textContent.includes('Sponsorowane');
// Sprawdzamy czy przycisk "Edytuj" już istnieje (tylko jeśli funkcja jest włączona)
if (window.vSprintPlusSettings && window.vSprintPlusSettings.edytuj !== false && article.querySelector("button.edytuj-btn")) return;
// Sprawdzamy czy przycisk "Kopiuj ID" już istnieje (tylko jeśli funkcja jest włączona)
if (window.vSprintPlusSettings && window.vSprintPlusSettings.kopiujId !== false && article.querySelector("button.copy-id-btn")) return;
// Pobieramy auctionID z linku w artykule
var auctionID = getAuctionID(article);
var title = getArticleTitle(article); // Pobieramy tytuł artykułu
// Debug dla ofert sponsorowanych
if (isSponsored && !auctionID) {
console.log("❌ Nie można pobrać ID z oferty sponsorowanej:", article);
var links = article.querySelectorAll("a");
console.log("🔗 Znalezione linki:", links.length);
links.forEach(function(link, index) {
console.log(`Link ${index}:`, link.href);
});
}
// Jeśli nie udało się uzyskać ID, pomiń artykuł
if (!auctionID) return;
// Tworzymy przycisk "Edytuj" tylko jeśli funkcja jest włączona
var edytujButton = null;
if (window.vSprintPlusSettings && window.vSprintPlusSettings.edytuj !== false) {
edytujButton = document.createElement("button");
edytujButton.className = "edytuj-btn";
edytujButton.innerHTML = "Edytuj";
edytujButton.style.position = "absolute";
edytujButton.style.right = "0px";
edytujButton.style.bottom = "142px"; // Nad przyciskiem Kopiuj ID
edytujButton.style.padding = "10px 16px";
edytujButton.style.backgroundColor = "#6c757d";
edytujButton.style.color = "#fff";
edytujButton.style.border = "none";
edytujButton.style.borderRadius = "5px";
edytujButton.style.cursor = "pointer";
edytujButton.style.zIndex = "200";
edytujButton.style.fontSize = "12px";
}
// Tworzymy przycisk "Kopiuj ID" tylko jeśli funkcja jest włączona
var copyIdButton = null;
if (window.vSprintPlusSettings && window.vSprintPlusSettings.kopiujId !== false) {
copyIdButton = document.createElement("button");
copyIdButton.className = "copy-id-btn";
copyIdButton.innerHTML = "Kopiuj ID";
copyIdButton.style.position = "absolute";
copyIdButton.style.right = "0px";
copyIdButton.style.bottom = "102px"; // Nad przyciskiem Statystyka
copyIdButton.style.padding = "10px 16px";
copyIdButton.style.backgroundColor = "#28a745";
copyIdButton.style.color = "#fff";
copyIdButton.style.border = "none";
copyIdButton.style.borderRadius = "5px";
copyIdButton.style.cursor = "pointer";
copyIdButton.style.zIndex = "200";
copyIdButton.style.fontSize = "12px";
}
// Tworzymy nowy element przycisku Statystyka
var button = document.createElement("button");
button.className = "statystyka-btn"; // Dodajemy klasę do identyfikacji
button.innerHTML = "Statystyka";
button.style.position = "absolute";
button.style.right = "0px";
button.style.bottom = "62px";
button.style.padding = "10px 20px";
button.style.backgroundColor = "#007bff";
button.style.color = "#fff";
button.style.border = "none";
button.style.borderRadius = "5px";
button.style.cursor = "pointer";
button.style.zIndex = "200"; // Ustawienie przycisku na wierzchu
// Ustawiamy pozycję przycisków w obrębie article
article.style.position = "relative";
if (edytujButton) {
article.appendChild(edytujButton);
}
if (copyIdButton) {
article.appendChild(copyIdButton);
}
article.appendChild(button);
// Dodajemy funkcjonalność przycisku Edytuj
if (edytujButton) {
edytujButton.addEventListener("click", function(event) {
event.stopPropagation(); // Zapobiegamy propagacji kliknięcia
openEditPage(auctionID); // Otwieramy stronę edycji oferty
});
}
// Dodajemy funkcjonalność przycisku Kopiuj ID
if (copyIdButton) {
copyIdButton.addEventListener("click", function(event) {
event.stopPropagation(); // Zapobiegamy propagacji kliknięcia
copyToClipboard(auctionID); // Kopiujemy ID do schowka
});
}
// Dodajemy funkcjonalność przycisku Statystyka
button.addEventListener("click", function(event) {
event.stopPropagation(); // Zapobiegamy propagacji kliknięcia
openModal(auctionID, title); // Otwieramy modalne okno
});
});
}
// Funkcja do pobrania auctionID z linku w artykule
function getAuctionID(article) {
// Najpierw spróbuj standardowego linku
var linkElement = article.querySelector("a[href*='/oferta/']");
if (linkElement) {
var link = linkElement.getAttribute("href");
var auctionID = link.split("-").pop();
return auctionID;
}
// Dla ofert sponsorowanych - szukaj linków z redirect=
var allLinks = article.querySelectorAll("a");
for (var i = 0; i < allLinks.length; i++) {
var href = allLinks[i].getAttribute("href");
if (href && href.includes("redirect=")) {
// Dekoduj URL i wyciągnij ID
try {
var decodedUrl = decodeURIComponent(href);
var match = decodedUrl.match(/allegro\.pl\/oferta\/([^?]+)/);
if (match) {
var auctionID = match[1].split("-").pop();
return auctionID;
}
} catch (e) {
// Ignoruj błędy dekodowania
}
}
}
return null;
}
// Funkcja do pobrania tytułu artykułu
function getArticleTitle(article) {
var h2Element = article.querySelector("h2");
return h2Element ? h2Element.textContent.trim() : "";
}
// Funkcja do kopiowania ID do schowka
function copyToClipboard(text) {
// Sprawdzamy czy GM_setClipboard jest dostępne (Tampermonkey)
if (typeof GM_setClipboard !== 'undefined') {
GM_setClipboard(text);
showCopyNotification('ID skopiowane do schowka!');
} else {
// Fallback dla przeglądarek bez GM_setClipboard
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(text).then(function() {
showCopyNotification('ID skopiowane do schowka!');
}).catch(function(err) {
console.error('Błąd kopiowania do schowka:', err);
fallbackCopyTextToClipboard(text);
});
} else {
fallbackCopyTextToClipboard(text);
}
}
}
// Fallback funkcja kopiowania dla starszych przeglądarek
function fallbackCopyTextToClipboard(text) {
var textArea = document.createElement("textarea");
textArea.value = text;
textArea.style.top = "0";
textArea.style.left = "0";
textArea.style.position = "fixed";
textArea.style.opacity = "0";
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
var successful = document.execCommand('copy');
if (successful) {
showCopyNotification('ID skopiowane do schowka!');
} else {
showCopyNotification('Nie udało się skopiować ID', 'error');
}
} catch (err) {
console.error('Błąd kopiowania:', err);
showCopyNotification('Nie udało się skopiować ID', 'error');
}
document.body.removeChild(textArea);
}
// Funkcja do wyświetlania powiadomienia o kopiowaniu
function showCopyNotification(message, type) {
// Usuń poprzednie powiadomienie jeśli istnieje
var existingNotification = document.querySelector('.copy-notification');
if (existingNotification) {
existingNotification.remove();
}
var notification = document.createElement('div');
notification.className = 'copy-notification';
notification.textContent = message;
notification.style.position = 'fixed';
notification.style.top = '20px';
notification.style.right = '20px';
notification.style.backgroundColor = type === 'error' ? '#dc3545' : '#28a745';
notification.style.color = '#fff';
notification.style.padding = '12px 20px';
notification.style.borderRadius = '5px';
notification.style.zIndex = '10001';
notification.style.fontSize = '14px';
notification.style.fontWeight = 'bold';
notification.style.boxShadow = '0 2px 10px rgba(0,0,0,0.3)';
notification.style.transition = 'opacity 0.3s ease';
document.body.appendChild(notification);
// Usuń powiadomienie po 3 sekundach
setTimeout(function() {
notification.style.opacity = '0';
setTimeout(function() {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
}, 300);
}, 3000);
}
// Funkcja do otwierania strony edycji oferty
function openEditPage(auctionID) {
if (!auctionID) {
showCopyNotification('Nie udało się pobrać ID oferty', 'error');
return;
}
const editUrl = `https://salescenter.allegro.com/offer/${auctionID}`;
// Otwieramy link w nowej karcie
window.open(editUrl, '_blank');
// Wyświetlamy powiadomienie o otwarciu
showCopyNotification('Otwieranie strony edycji oferty...', 'success');
}
// Funkcja do tworzenia i wyświetlania modalnego okna
// Funkcja do tworzenia i wyświetlania modalnego okna
function openModal(auctionID, title) {
// Tworzymy modalne okno, jeśli jeszcze nie istnieje
var existingModal = document.querySelector("#statystyka-modal");
if (!existingModal) {
var modal = document.createElement("div");
modal.id = "statystyka-modal";
modal.style.position = "fixed";
modal.style.top = "0";
modal.style.right = "0";
modal.style.width = "35%";
modal.style.height = "100%";
modal.style.backgroundColor = "#ffffff";
modal.style.overflowY = "auto";
modal.style.boxShadow = "0 0 10px rgba(0, 0, 0, 0.2)";
modal.style.transform = "translateX(100%)";
modal.style.transition = "transform 0.3s ease";
modal.style.zIndex = "10000";
modal.style.color = "#333";
modal.style.fontFamily = "Arial, sans-serif";
var content = document.createElement("div");
content.style.padding = "20px";
content.style.position = "relative";
content.style.lineHeight = "1.6";
var closeButton = document.createElement("span");
closeButton.innerHTML = "×";
closeButton.style.position = "absolute";
closeButton.style.top = "10px";
closeButton.style.right = "10px";
closeButton.style.cursor = "pointer";
closeButton.style.fontSize = "24px";
closeButton.style.color = "#007bff";
closeButton.addEventListener("click", function () {
modal.style.transform = "translateX(100%)";
setTimeout(function () {
modal.remove();
}, 300);
});
var modalContent = document.createElement("div");
modalContent.id = "statystyka-content";
// Sprawdzamy czy skrypty już są załadowane
if (!window.flatpickr || !window.Chart) {
// Dodaj Flatpickr i lokalizację polską tylko jeśli nie są już załadowane
var script = document.createElement('script');
var chartscript = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/flatpickr';
chartscript.src="https://cdn.jsdelivr.net/npm/chart.js"
document.head.appendChild(script);
document.head.appendChild(chartscript);
var localeScript = document.createElement('script');
localeScript.src = 'https://cdn.jsdelivr.net/npm/flatpickr/dist/l10n/pl.js';
document.head.appendChild(localeScript);
// Upewnij się, że oba skrypty są załadowane, zanim zainicjalizujesz Flatpickr
Promise.all([
new Promise(resolve => { script.onload = resolve; }),
new Promise(resolve => { localeScript.onload = resolve; })
]).then(() => {
initializeModalContent();
});
} else {
// Skrypty już są załadowane, od razu inicjalizuj
initializeModalContent();
}
function initializeModalContent() {
// Sprawdzenie, czy elementy #start-date i #end-date istnieją przed inicjalizacją Flatpickr
var startDateInput = document.querySelector("#start-date");
var endDateInput = document.querySelector("#end-date");
// Funkcja do zapisywania dat do localStorage
function saveDateToLocalStorage() {
if (startDateInput && endDateInput) {
localStorage.setItem("start-date", startDateInput.value);
localStorage.setItem("end-date", endDateInput.value);
}
}
// Funkcja do ładowania dat z localStorage
function loadDateFromLocalStorage() {
var storedStartDate = localStorage.getItem("start-date");
var storedEndDate = localStorage.getItem("end-date");
if (storedStartDate) {
startDateInput.value = storedStartDate;
}
if (storedEndDate) {
endDateInput.value = storedEndDate;
}
}
if (startDateInput) {
flatpickr(startDateInput, {
dateFormat: "Y-m-d",
disableMobile: true,
locale: 'pl', // Ustawienie lokalizacji na polski
allowInput: false, // Zablokuj ręczne wpisywanie dat
onChange: saveDateToLocalStorage // Zapisuj datę po każdej zmianie
});
}
if (endDateInput) {
flatpickr(endDateInput, {
dateFormat: "Y-m-d",
disableMobile: true,
locale: 'pl', // Ustawienie lokalizacji na polski
allowInput: false, // Zablokuj ręczne wpisywanie dat
onChange: saveDateToLocalStorage // Zapisuj datę po każdej zmianie
});
}
// Załaduj daty z localStorage przy ładowaniu modala
loadDateFromLocalStorage();
// Dodanie przycisku "Poprzedni tydzień"
var prevWeekButton = document.createElement("button");
prevWeekButton.innerText = "Poprzedni tydzień";
prevWeekButton.className = "date-button"; // Dodajemy klasę CSS
// Funkcja do obliczenia dat poprzedniego tygodnia
prevWeekButton.addEventListener("click", function () {
var today = new Date();
var lastSunday = new Date(today.setDate(today.getDate() - today.getDay() - 7)); // Ostatnia niedziela sprzed tygodnia
var lastMonday = new Date(today.setDate(today.getDate() - 6)); // Poniedziałek przed ostatnią niedzielą
startDateInput.value = lastMonday.toISOString().split('T')[0]; // Ustaw pierwszy dzień tygodnia (poniedziałek)
endDateInput.value = lastSunday.toISOString().split('T')[0]; // Ustaw ostatni dzień tygodnia (niedziela)
});
// Dodanie przycisku "Ostatnie 7 dni"
var last7DaysButton = document.createElement("button");
last7DaysButton.innerText = "Ostatnie 7 dni";
last7DaysButton.className = "date-button"; // Dodajemy klasę CSS
// Dodanie przycisku "Ostatnie 14 dni"
var last14DaysButton = document.createElement("button");
last14DaysButton.innerText = "Ostatnie 14 dni";
last14DaysButton.className = "date-button"; // Dodajemy klasę CSS
// Dodanie przycisku "Ostatnie 30 dni (maksymalny zakres)"
var last30DaysButton = document.createElement("button");
last30DaysButton.innerText = "Ostatnie 30 dni (maksymalny zakres)";
last30DaysButton.className = "date-button"; // Dodajemy klasę CSS
// Funkcja do obliczenia dat ostatnich 7 dni
last7DaysButton.addEventListener("click", function () {
var yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1); // Ustaw wczoraj
var startDate = new Date(yesterday);
startDate.setDate(startDate.getDate() - 6); // 7 dni wstecz od wczoraj
startDateInput.value = startDate.toISOString().split('T')[0]; // Ustaw pierwszy dzień
endDateInput.value = yesterday.toISOString().split('T')[0]; // Ustaw wczorajszy dzień
});
// Funkcja do obliczenia dat ostatnich 14 dni
last14DaysButton.addEventListener("click", function () {
var yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1); // Ustaw wczoraj
var startDate = new Date(yesterday);
startDate.setDate(startDate.getDate() - 13); // 14 dni wstecz od wczoraj
startDateInput.value = startDate.toISOString().split('T')[0]; // Ustaw pierwszy dzień
endDateInput.value = yesterday.toISOString().split('T')[0]; // Ustaw wczorajszy dzień
});
// Funkcja do obliczenia dat ostatnich 30 dni
last30DaysButton.addEventListener("click", function () {
var today = new Date();
var startDate = new Date(today);
startDate.setDate(startDate.getDate() - 29); // 30 dni wstecz od dzisiaj (włącznie z dzisiaj)
startDateInput.value = startDate.toISOString().split('T')[0]; // Ustaw pierwszy dzień
endDateInput.value = today.toISOString().split('T')[0]; // Ustaw dzisiaj jako datę końcową
});
// Dodanie przycisku "Wczoraj"
var yesterdayButton = document.createElement("button");
yesterdayButton.innerText = "Wczoraj";
yesterdayButton.className = "date-button"; // Dodajemy klasę CSS
// Funkcja do ustawiania daty wczorajszej
yesterdayButton.addEventListener("click", function () {
var yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1); // Ustaw wczoraj
startDateInput.value = yesterday.toISOString().split('T')[0]; // Ustaw dzień wczorajszy jako datę początkową
endDateInput.value = yesterday.toISOString().split('T')[0]; // Ustaw dzień wczorajszy jako datę końcową
});
// Tworzenie kontenera na przyciski
var DataButtonContainer = document.createElement("div");
DataButtonContainer.className = "DataButton-container"; // Możesz dodać klasę CSS do stylizacji
// Dodaj przyciski do kontenera
DataButtonContainer.appendChild(yesterdayButton);
DataButtonContainer.appendChild(prevWeekButton);
DataButtonContainer.appendChild(last7DaysButton);
DataButtonContainer.appendChild(last14DaysButton);
DataButtonContainer.appendChild(last30DaysButton);
// Zidentyfikuj przycisk "Pobierz dane"
var fetchDataButton = dateForm.querySelector("#fetch-data-btn");
// Dodaj kontener z przyciskami przed przyciskiem "Pobierz dane"
dateForm.insertBefore(DataButtonContainer, fetchDataButton);
// Dodaj formularz do modalnego okna
modalContent.appendChild(dateForm);
}
// Dodanie formularza wyboru dat
var dateForm = document.createElement("div");
dateForm.innerHTML = `
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
<div class="date-container">
<label for="start-date">Data początkowa:</label>
<input type="text" id="start-date" placeholder="Wybierz datę">
</div>
<div class="date-container">
<label for="end-date">Data końcowa:</label>
<input type="text" id="end-date" placeholder="Wybierz datę">
</div>
<button id="fetch-data-btn">Pobierz dane</button>
`;
dateForm.style.marginBottom = "20px";
content.appendChild(closeButton);
content.appendChild(dateForm);
content.appendChild(modalContent);
modal.appendChild(content);
document.body.appendChild(modal);
// Otwórz modalne okno z animacją
setTimeout(function () {
modal.style.transform = "translateX(0)";
}, 10);
// Wywołujemy funkcję do pobrania danych i wypełnienia modalnego okna
document.querySelector("#fetch-data-btn").addEventListener("click", function () {
var startDate = document.querySelector("#start-date").value;
var endDate = document.querySelector("#end-date").value;
fetchModalData(auctionID, title, startDate, endDate);
});
} else {
existingModal.style.transform = "translateX(0)"; // Upewniamy się, że modalne okno jest widoczne
}
}
// Funkcja do pobierania danych i wypełniania modalnego okna
async function fetchModalData(auctionID, title, startDate, endDate) {
var modalContent = document.querySelector("#statystyka-content");
if (!modalContent) return;
// Wyświetlamy progress bar z statusem dla każdego fetcha
modalContent.innerHTML = `
<h2 style="margin-top: 0;">Statystyki dla oferty ${auctionID}</h2>
<div id="fetch-progress" style="border: 1px solid #ddd; border-radius: 8px; padding: 15px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 20px; background-color: #f8f9fa;">
<h3 style="position: relative;">
Pobieranie danych...
<button id="retry-failed-fetches" style="display: none; position: absolute; right: 0; top: 50%; transform: translateY(-50%); background-color: #28a745; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; font-size: 14px; font-weight: bold;">
🔄 Spróbuj ponownie
</button>
</h3>
<div id="fetch-status-deals" class="fetch-status" style="display: flex; align-items: center; padding: 12px 0; border-bottom: 1px solid #eee;">
<span style="flex: 1;">📊 Dane sprzedaży</span>
<span id="status-deals" style="color: #6c757d; margin-left: 15px; padding-right: 5px;">⏳ Oczekiwanie...</span>
</div>
<div id="fetch-status-prices" class="fetch-status" style="display: flex; align-items: center; padding: 12px 0; border-bottom: 1px solid #eee;">
<span style="flex: 1;">💰 Historia cen</span>
<span id="status-prices" style="color: #6c757d; margin-left: 15px; padding-right: 5px;">⏳ Oczekiwanie...</span>
</div>
<div id="fetch-status-conversion" class="fetch-status" style="display: flex; align-items: center; padding: 12px 0; border-bottom: 1px solid #eee;">
<span style="flex: 1;">🔄 Konwersja oferty</span>
<span id="status-conversion" style="color: #6c757d; margin-left: 15px; padding-right: 5px;">⏳ Oczekiwanie...</span>
</div>
<div id="fetch-status-product" class="fetch-status" style="display: flex; align-items: center; padding: 12px 0; border-bottom: 1px solid #eee;">
<span style="flex: 1;">📈 Konwersja produktu</span>
<span id="status-product" style="color: #6c757d; margin-left: 15px; padding-right: 5px;">⏳ Oczekiwanie...</span>
</div>
<div id="fetch-status-promotion" class="fetch-status" style="display: flex; align-items: center; padding: 12px 0; border-bottom: 1px solid #eee;">
<span style="flex: 1;">🎯 Promowania</span>
<span id="status-promotion" style="color: #6c757d; margin-left: 15px; padding-right: 5px;">⏳ Oczekiwanie...</span>
</div>
<div id="fetch-status-keywords" class="fetch-status" style="display: flex; align-items: center; padding: 12px 0;">
<span style="flex: 1;">🔑 Słowa kluczowe</span>
<span id="status-keywords" style="color: #6c757d; margin-left: 15px; padding-right: 5px;">⏳ Oczekiwanie...</span>
</div>
</div>
<div id="loading-spinner" style="display: flex; justify-content: center; align-items: center;">
<div style="border: 4px solid #f3f3f3; border-radius: 50%; border-top: 4px solid #007bff; width: 40px; height: 40px; animation: spin 1s linear infinite;"></div>
</div>
<style>
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.fetch-status {
transition: all 0.3s ease;
}
</style>
`;
// Inicjalizujemy obiekt do przechowywania danych z obsługą błędów
const dataResults = {
salesData: null,
priceMonitorData: null,
conversionData: null,
conversionDataExtended: null,
promotionData: null,
keywordsData: null,
errors: [],
failedFetches: [] // Śledzenie nieudanych fetchów
};
// Funkcja do formatowania błędów HTTP
function formatHttpError(status) {
switch (status) {
case 401:
return 'Brak dostępu, zaloguj się na właściwe konto Analytics';
case 429:
return 'Za dużo zapytań';
case 403:
return 'Błąd 403';
case 404:
return 'Nie znaleziono';
case 500:
return 'Błąd serwera';
case 503:
return 'Serwis niedostępny';
default:
return `Błąd HTTP: ${status}`;
}
}
// Funkcja do aktualizowania statusu fetcha
function updateFetchStatus(fetchName, status, message) {
const statusElement = document.getElementById(`status-${fetchName}`);
const fetchElement = document.getElementById(`fetch-status-${fetchName}`);
if (statusElement && fetchElement) {
let icon, color;
switch (status) {
case 'loading':
icon = '⏳';
color = '#007bff';
break;
case 'success':
icon = '✅';
color = '#28a745';
// Usuń z listy nieudanych fetchów jeśli był tam
dataResults.failedFetches = dataResults.failedFetches.filter(f => f !== fetchName);
break;
case 'error':
icon = '❌';
color = '#dc3545';
// Dodaj do listy nieudanych fetchów
if (!dataResults.failedFetches.includes(fetchName)) {
dataResults.failedFetches.push(fetchName);
}
break;
case 'skipped':
icon = '⏭️';
color = '#6c757d';
// Nie dodawaj do listy nieudanych fetchów dla "Niemożliwe"
if (message !== 'Niemożliwe' && !dataResults.failedFetches.includes(fetchName)) {
dataResults.failedFetches.push(fetchName);
}
break;
default:
icon = '⏳';
color = '#6c757d';
}
statusElement.innerHTML = `${icon} ${message}`;
statusElement.style.color = color;
if (status === 'success') {
fetchElement.style.backgroundColor = '#d4edda';
fetchElement.style.borderLeft = '4px solid #28a745';
} else if (status === 'error') {
fetchElement.style.backgroundColor = '#f8d7da';
fetchElement.style.borderLeft = '4px solid #dc3545';
} else if (status === 'skipped') {
fetchElement.style.backgroundColor = '#e2e3e5';
fetchElement.style.borderLeft = '4px solid #6c757d';
}
// Pokaż/ukryj przycisk retry (tylko jeśli przycisk już istnieje)
setTimeout(() => updateRetryButton(), 100);
}
}
// Funkcja do aktualizowania przycisku retry
function updateRetryButton() {
const retryButton = document.getElementById('retry-failed-fetches');
if (retryButton) {
if (dataResults.failedFetches.length > 0) {
retryButton.style.display = 'block';
retryButton.textContent = `🔄 Spróbuj ponownie (${dataResults.failedFetches.length})`;
} else {
retryButton.style.display = 'none';
}
}
}
try {
// Fetch 1: Dane sprzedażowe (niezależny)
updateFetchStatus('deals', 'loading', 'Pobieranie...');
try {
const response1 = await fetch("https://edge.allegro.pl/analytics/auction/deals", {
"credentials": "include",
"headers": {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0",
"Accept": "application/vnd.allegro.internal.v1+json",
"Accept-Language": "pl-PL",
"Content-Type": "application/vnd.allegro.internal.v1+json",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-site",
"Priority": "u=0",
"Pragma": "no-cache",
"Cache-Control": "no-cache"
},
"referrer": "https://allegro.pl/",
"body": JSON.stringify({
"includePhrasesMode": "OfferOrProduct",
"excludePhrasesMode": "OfferOrProduct",
"auctionId": auctionID,
"sellerIds": [],
"marketplaceIds": ["allegro-pl"],
"startDate": startDate,
"endDate": endDate,
"startDateTime": startDate + "T00:00:00.000Z",
"endDateTime": endDate + "T23:59:59.999Z",
"reportCode": "TOP7D"
}),
"method": "POST",
"mode": "cors"
});
if (response1.ok) {
const data1 = await response1.json();
if (data1 && data1.result && data1.result.saleInfo) {
dataResults.salesData = data1;
updateFetchStatus('deals', 'success', 'Pobrano pomyślnie');
console.log("✅ Pobrano dane sprzedaży:", data1);
console.log("🔍 pmdProductIds:", data1.result.pmdProductIds);
} else {
updateFetchStatus('deals', 'error', 'Nieprawidłowa struktura danych');
dataResults.errors.push("Nieprawidłowa struktura danych sprzedaży");
}
} else {
updateFetchStatus('deals', 'error', formatHttpError(response1.status));
dataResults.errors.push(`Błąd HTTP sprzedaży: ${response1.status}`);
}
} catch (error) {
updateFetchStatus('deals', 'error', `Błąd: ${error.message}`);
dataResults.errors.push(`Błąd pobierania danych sprzedaży: ${error.message}`);
console.error("Błąd pobierania danych sprzedaży:", error);
}
// Fetch 2: Monitorowanie cen (niezależny)
updateFetchStatus('prices', 'loading', 'Pobieranie...');
try {
const response2 = await fetch("https://edge.allegro.pl/analytics/reports/auctions/pricemonitor/pricejournal", {
"credentials": "include",
"headers": {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0",
"Accept": "application/vnd.allegro.internal.v1+json",
"Accept-Language": "pl-PL",
"Content-Type": "application/vnd.allegro.internal.v1+json",
"Priority": "u=4",
"Pragma": "no-cache",
"Cache-Control": "no-cache",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-site"
},
"referrer": "https://allegro.pl/",
"body": JSON.stringify({
"includePhrases": title,
"includePhrasesMode": "OfferOrProduct",
"excludePhrasesMode": "OfferOrProduct",
"auctionType": 1,
"sellerIds": [],
"skip": 0,
"take": 1000,
"marketplaceIds": ["allegro-pl"],
"startDate": startDate + "T00:00:00.000Z",
"endDate": endDate + "T00:00:00.000Z",
"startDateTime": startDate + "T00:00:00.000Z",
"endDateTime": endDate + "T23:59:59.999Z",
"reportCode": "TOP7D"
}),
"method": "POST",
"mode": "cors"
});
if (response2.ok) {
const data2 = await response2.json();
if (data2 && data2.entries) {
dataResults.priceMonitorData = data2;
updateFetchStatus('prices', 'success', 'Pobrano pomyślnie');
console.log("✅ Pobrano dane monitorowania cen:", data2);
} else {
updateFetchStatus('prices', 'error', 'Nieprawidłowa struktura danych');
dataResults.errors.push("Nieprawidłowa struktura danych monitorowania cen");
}
} else {
updateFetchStatus('prices', 'error', formatHttpError(response2.status));
dataResults.errors.push(`Błąd HTTP monitorowania cen: ${response2.status}`);
}
} catch (error) {
updateFetchStatus('prices', 'error', `Błąd: ${error.message}`);
dataResults.errors.push(`Błąd pobierania danych monitorowania cen: ${error.message}`);
console.error("Błąd pobierania danych monitorowania cen:", error);
}
// Fetch 3: Konwersja (zależy od productid z salesData)
console.log("🔍 Sprawdzanie productid:", dataResults.salesData?.result?.pmdProductIds);
if (dataResults.salesData && dataResults.salesData.result.pmdProductIds && dataResults.salesData.result.pmdProductIds.length > 0) {
updateFetchStatus('conversion', 'loading', 'Pobieranie...');
try {
const productid = dataResults.salesData.result.pmdProductIds[0];
const response3 = await fetch("https://edge.allegro.pl/analytics/reports/sale/byconversion", {
"credentials": "include",
"headers": {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:130.0) Gecko/20100101 Firefox/130.0",
"Accept": "application/vnd.allegro.internal.v1+json",
"Accept-Language": "pl-PL",
"Content-Type": "application/vnd.allegro.internal.v1+json",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-site",
"Priority": "u=0",
"Pragma": "no-cache",
"Cache-Control": "no-cache"
},
"referrer": "https://allegro.pl/",
"body": JSON.stringify({
"includePhrases": productid,
"includePhrasesMode": "AnyId",
"excludePhrasesMode": "AnyId",
"sellerIds": [],
"includeCancelledAndReturned": true,
"skip": 0,
"take": 1000,
"marketplaceIds": ["allegro-pl"],
"startDate": startDate + "T00:00:00.000Z",
"endDate": endDate + "T00:00:00.000Z",
"startDateTime": startDate + "T00:00:00.000Z",
"endDateTime": endDate + "T23:59:59.999Z",
"reportCode": "TOP7D"
}),
"method": "POST",
"mode": "cors"
});
if (response3.ok) {
const data3 = await response3.json();
if (data3 && data3.result && data3.result.entries) {
dataResults.conversionData = data3;
updateFetchStatus('conversion', 'success', 'Pobrano pomyślnie');
console.log("✅ Pobrano dane konwersji:", data3);
} else {
updateFetchStatus('conversion', 'error', 'Nieprawidłowa struktura danych');
dataResults.errors.push("Nieprawidłowa struktura danych konwersji");
}
} else {
updateFetchStatus('conversion', 'error', formatHttpError(response3.status));
dataResults.errors.push(`Błąd HTTP konwersji: ${response3.status}`);
}
} catch (error) {
updateFetchStatus('conversion', 'error', `Błąd: ${error.message}`);
dataResults.errors.push(`Błąd pobierania danych konwersji: ${error.message}`);
console.error("Błąd pobierania danych konwersji:", error);
}
} else {
const productIds = dataResults.salesData?.result?.pmdProductIds;
if (!productIds) {
updateFetchStatus('conversion', 'skipped', 'Niemożliwe');
dataResults.errors.push("Brak pmdProductIds w odpowiedzi - pominięto pobieranie danych konwersji");
} else if (productIds.length === 0) {
updateFetchStatus('conversion', 'skipped', 'Niemożliwe');
dataResults.errors.push("Pusta tablica pmdProductIds - pominięto pobieranie danych konwersji");
} else {
updateFetchStatus('conversion', 'skipped', 'Niemożliwe');
dataResults.errors.push("Nieprawidłowe pmdProductIds - pominięto pobieranie danych konwersji");
}
}
// Fetch 4: Rozszerzone dane konwersji (zależy od pmdProductName z conversionData)
if (dataResults.conversionData && dataResults.conversionData.result && dataResults.conversionData.result.entries) {
const conversionDataAfter = dataResults.conversionData.result.entries.filter(entry => entry.ids.includes(auctionID));
const pmdProductName = conversionDataAfter.length ? conversionDataAfter[0].pmdProductName : null;
if (pmdProductName) {
updateFetchStatus('product', 'loading', 'Pobieranie...');
try {
const response4 = await fetch("https://edge.allegro.pl/analytics/reports/sale/byconversion", {
"credentials": "include",
"headers": {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:130.0) Gecko/20100101 Firefox/130.0",
"Accept": "application/vnd.allegro.internal.v1+json",
"Accept-Language": "pl-PL",
"Content-Type": "application/vnd.allegro.internal.v1+json",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-site"
},
"referrer": "https://allegro.pl/",
"body": JSON.stringify({
"includePhrases": pmdProductName,
"includePhrasesMode": "Product",
"excludePhrasesMode": "Product",
"auctionType": 1,
"sellerIds": [],
"skip": 0,
"take": 1000,
"marketplaceIds": ["allegro-pl"],
"startDate": startDate + "T00:00:00.000Z",
"endDate": endDate + "T00:00:00.000Z",
"startDateTime": startDate + "T00:00:00.000Z",
"endDateTime": endDate + "T23:59:59.999Z",
"reportCode": "TOP7D"
}),
"method": "POST",
"mode": "cors"
});
if (response4.ok) {
const data4 = await response4.json();
if (data4 && data4.result) {
dataResults.conversionDataExtended = data4;
updateFetchStatus('product', 'success', 'Pobrano pomyślnie');
console.log("✅ Pobrano rozszerzone dane konwersji:", data4);
} else {
updateFetchStatus('product', 'error', 'Nieprawidłowa struktura danych');
dataResults.errors.push("Nieprawidłowa struktura rozszerzonych danych konwersji");
}
} else {
updateFetchStatus('product', 'error', formatHttpError(response4.status));
dataResults.errors.push(`Błąd HTTP rozszerzonych danych konwersji: ${response4.status}`);
}
} catch (error) {
updateFetchStatus('product', 'error', `Błąd: ${error.message}`);
dataResults.errors.push(`Błąd pobierania rozszerzonych danych konwersji: ${error.message}`);
console.error("Błąd pobierania rozszerzonych danych konwersji:", error);
}
} else {
updateFetchStatus('product', 'skipped', 'Niemożliwe');
dataResults.errors.push("Brak pmdProductName - pominięto rozszerzone dane konwersji");
}
} else {
updateFetchStatus('product', 'skipped', 'Niemożliwe');
dataResults.errors.push("Brak danych konwersji - pominięto rozszerzone dane konwersji");
}
// Fetch 5: Dane promocji (zależny od danych sprzedaży)
updateFetchStatus('promotion', 'loading', 'Pobieranie...');
try {
// Sprawdzamy czy mamy dane sprzedaży z pmdProductIds
if (!dataResults.salesData || !dataResults.salesData.result.pmdProductIds || dataResults.salesData.result.pmdProductIds.length === 0) {
updateFetchStatus('promotion', 'skipped', 'Niemożliwe');
dataResults.errors.push("Brak pmdProductIds - pominięto pobieranie danych promocji");
} else {
const productId = dataResults.salesData.result.pmdProductIds[0];
const sellerId = dataResults.salesData.result.seller?.id;
const response5 = await fetch("https://edge.allegro.pl/analytics/reports/sale/bypromotion", {
"credentials": "include",
"headers": {
"Accept": "application/vnd.allegro.internal.v1+json",
"Accept-Language": "pl-PL",
"Content-Type": "application/vnd.allegro.internal.v1+json",
"Priority": "u=1, i",
"Sec-CH-UA": "\"Google Chrome\";v=\"129\", \"Not=A?Brand\";v=\"8\", \"Chromium\";v=\"129\"",
"Sec-CH-UA-Mobile": "?0",
"Sec-CH-UA-Platform": "\"Windows\"",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-site"
},
"referrer": "https://allegro.pl/",
"referrerPolicy": "strict-origin-when-cross-origin",
"body": JSON.stringify({
"includePhrases": productId,
"includePhrasesMode": "AnyId",
"excludePhrasesMode": "AnyId",
"sellerIds": [],
"marketplaceIds": ["allegro-pl"],
"startDate": startDate + "T00:00:00.000Z",
"endDate": endDate + "T00:00:00.000Z",
"startDateTime": startDate + "T02:00:00.000Z",
"endDateTime": endDate + "T23:59:59.999Z",
"reportCode": "LASTFULL7DAYS"
}),
"method": "POST",
"mode": "cors"
});
if (response5.ok) {
const data5 = await response5.json();
if (data5 && data5.result && data5.result.entries) {
dataResults.promotionData = data5;
updateFetchStatus('promotion', 'success', 'Pobrano pomyślnie');
console.log("✅ Pobrano dane promocji:", data5);
} else {
updateFetchStatus('promotion', 'error', 'Nieprawidłowa struktura danych');
dataResults.errors.push("Nieprawidłowa struktura danych promocji");
}
} else {
updateFetchStatus('promotion', 'error', formatHttpError(response5.status));
dataResults.errors.push(`Błąd HTTP promocji: ${response5.status}`);
}
}
} catch (error) {
updateFetchStatus('promotion', 'error', `Błąd: ${error.message}`);
dataResults.errors.push(`Błąd pobierania danych promocji: ${error.message}`);
console.error("Błąd pobierania danych promocji:", error);
}
// Fetch 6: Słowa kluczowe (zależy od productid z salesData)
if (dataResults.salesData && dataResults.salesData.result.pmdProductIds && dataResults.salesData.result.pmdProductIds.length > 0) {
updateFetchStatus('keywords', 'loading', 'Pobieranie...');
try {
const productId = dataResults.salesData.result.pmdProductIds[0];
const response6 = await fetch("https://edge.allegro.pl/analytics/reports/sale/bykeyword", {
"credentials": "include",
"headers": {
"Accept": "application/vnd.allegro.internal.v1+json",
"Accept-Language": "pl-PL",
"Content-Type": "application/vnd.allegro.internal.v1+json",
"Priority": "u=1, i",
"Sec-CH-UA": "\"Chromium\";v=\"140\", \"Not=A?Brand\";v=\"24\", \"Google Chrome\";v=\"140\"",
"Sec-CH-UA-Mobile": "?0",
"Sec-CH-UA-Platform": "\"Windows\"",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-site"
},
"referrer": "https://allegro.pl/",
"body": JSON.stringify({
"includePhrases": productId,
"includePhrasesMode": "AnyId",
"excludePhrasesMode": "OfferOrProduct",
"auctionId": auctionID,
"skip": 0,
"take": 1000,
"marketplaceIds": ["allegro-pl"],
"startDate": startDate + "T00:00:00.000Z",
"endDate": endDate + "T00:00:00.000Z",
"startDateTime": startDate + "T02:00:00.000Z",
"endDateTime": endDate + "T04:38:00.000Z",
"reportCode": "LASTFULL7DAYS"
}),
"method": "POST",
"mode": "cors"
});
if (response6.ok) {
const data6 = await response6.json();
if (data6 && data6.result && data6.result.entries) {
dataResults.keywordsData = data6;
updateFetchStatus('keywords', 'success', 'Pobrano pomyślnie');
console.log("✅ Pobrano dane słów kluczowych:", data6);
} else {
updateFetchStatus('keywords', 'error', 'Nieprawidłowa struktura danych');
dataResults.errors.push("Nieprawidłowa struktura danych słów kluczowych");
}
} else {
updateFetchStatus('keywords', 'error', formatHttpError(response6.status));
dataResults.errors.push(`Błąd HTTP słów kluczowych: ${response6.status}`);
}
} catch (error) {
updateFetchStatus('keywords', 'error', `Błąd: ${error.message}`);
dataResults.errors.push(`Błąd pobierania danych słów kluczowych: ${error.message}`);
console.error("Błąd pobierania danych słów kluczowych:", error);
}
} else {
updateFetchStatus('keywords', 'skipped', 'Niemożliwe');
dataResults.errors.push("Brak pmdProductIds - pominięto pobieranie danych słów kluczowych");
}
// Sprawdzamy czy mamy przynajmniej jakieś dane do wyświetlenia
if (!dataResults.salesData && !dataResults.priceMonitorData && !dataResults.conversionData && !dataResults.promotionData) {
// Sprawdź czy wystąpił błąd 401
const has401Error = dataResults.errors.some(errorMsg => errorMsg.includes('401'));
const errorMessage = has401Error ?
'Brak dostępu, zaloguj się na właściwe konto Analytics' :
'Nie udało się pobrać żadnych danych. Sprawdź połączenie internetowe i spróbuj ponownie.';
throw new Error(errorMessage);
}
// Wyodrębniamy dane z dostępnych źródeł z obsługą błędów
var totalSale = 0, todaySale = 0, yesterdaySale = 0, promotionCycles = [];
var offerStartDate = '', startingQuantity = 0, currentQuantity = 0, seller = '';
var productid = [], filtProductId = null;
if (dataResults.salesData) {
const salesData = dataResults.salesData.result;
totalSale = salesData.saleInfo?.totalSale || 0;
todaySale = salesData.saleInfo?.todaySale || 0;
yesterdaySale = salesData.saleInfo?.yesterdaySale || 0;
promotionCycles = salesData.promotionCyclesHistory || [];
offerStartDate = salesData.overallItemInfo?.startDate || '';
startingQuantity = salesData.startingQuantity || 0;
currentQuantity = salesData.currentQuantity || 0;
seller = salesData.seller?.name || '';
productid = salesData.pmdProductIds || [];
if (productid.length > 0) filtProductId = productid[0];
}
// Filtrowanie danych monitorowania cen
var filteredEntries = [];
if (dataResults.priceMonitorData && dataResults.priceMonitorData.entries) {
filteredEntries = dataResults.priceMonitorData.entries
.filter(entry => entry.id == auctionID)
.sort((a, b) => new Date(a.priceChange) - new Date(b.priceChange));
}
// Przetwarzanie danych konwersji
var conversionData = [];
var conversionDataAfter = [];
var pmdProductName = null;
var wholeConversion = [];
if (dataResults.conversionData && dataResults.conversionData.result && dataResults.conversionData.result.entries) {
conversionData = dataResults.conversionData.result.entries.filter(entry => entry.ids.includes(auctionID));
conversionDataAfter = dataResults.conversionData.result.entries.filter(entry => entry.ids.includes(auctionID));
pmdProductName = conversionDataAfter.length ? conversionDataAfter[0].pmdProductName : null;
}
// Przetwarzanie rozszerzonych danych konwersji
if (dataResults.conversionDataExtended && dataResults.conversionDataExtended.result) {
wholeConversion = dataResults.conversionDataExtended.result.entries.filter(entry => entry.pmdProductId === String(filtProductId));
}
// Przetwarzanie danych promocji
var entries = [];
if (dataResults.promotionData && dataResults.promotionData.result && dataResults.promotionData.result.entries) {
entries = dataResults.promotionData.result.entries;
}
// Zmienne do przechowywania danych
var FreeDeliveryAndPromotion = {};
var FreeDelivery = {};
var Promotion = {}; // Nowa zmienna dla entries.options === 4
// Funkcja do przekształcania efficiencyClass na odpowiednią wartość tekstową
function getEfficiencyClassText(efficiencyClass) {
switch (efficiencyClass) {
case 4:
return 'Bardzo wysoki';
case 3:
return 'Wysoki';
case 2:
return 'Średni';
case 1:
return 'Niski';
case 0:
return 'Bardzo niski';
default:
return 'Nieznany';
}
}
// Funkcja do przekształcania wartości dziesiętnej na procenty (np. 0.6146 na 61,46%)
function toPercentage(value) {
return (value * 100).toFixed(2) + '%';
}
// Przejdź przez wszystkie elementy w "entries" i wyciągnij potrzebne dane
entries.forEach(entry => {
if (entry.options === 68) {
FreeDeliveryAndPromotion.efficiencyClass = getEfficiencyClassText(entry.efficiencyClass);
FreeDeliveryAndPromotion.sale = toPercentage(entry.saleParticipation.sale);
FreeDeliveryAndPromotion.transactions = toPercentage(entry.saleParticipation.transactions);
} else if (entry.options === 64) {
FreeDelivery.efficiencyClass = getEfficiencyClassText(entry.efficiencyClass);
FreeDelivery.sale = toPercentage(entry.saleParticipation.sale);
FreeDelivery.transactions = toPercentage(entry.saleParticipation.transactions);
} else if (entry.options === 4) { // Dodane dla Promotion
Promotion.efficiencyClass = getEfficiencyClassText(entry.efficiencyClass);
Promotion.sale = toPercentage(entry.saleParticipation.sale);
Promotion.transactions = toPercentage(entry.saleParticipation.transactions);
}
});
// Wyświetl zmienne w konsoli
console.log("Dane dla FreeDeliveryAndPromotion:", FreeDeliveryAndPromotion);
console.log("Dane dla FreeDelivery:", FreeDelivery);
console.log("Dane dla Promotion:", Promotion);
// Teraz masz dostępne zmienne:
// FreeDeliveryAndPromotion.efficiencyClass
// FreeDeliveryAndPromotion.sale
// FreeDeliveryAndPromotion.transactions
// FreeDelivery.efficiencyClass
// FreeDelivery.sale
// FreeDelivery.transactions
console.log(FreeDeliveryAndPromotion);
console.log(FreeDelivery);
// Suma wszystkich transactions
var totalTransactions = wholeConversion.length > 0 ? wholeConversion.reduce((sum, entry) => sum + entry.transactions, 0) : 0;
// Średnia liczba transakcji
var averageTransactions = wholeConversion.length > 0 ? totalTransactions / wholeConversion.length : 0;
// Średnia konwersji
var totalConversion = wholeConversion.length > 0 ? wholeConversion.reduce((sum, entry) => sum + entry.conversion, 0) : 0;
var averageConversion = wholeConversion.length > 0 ? totalConversion / wholeConversion.length : 0;
// Średnia liczba wizyt
var totalViewsCount = wholeConversion.length > 0 ? wholeConversion.reduce((sum, entry) => sum + entry.viewsCount, 0) : 0;
var averageViewsCount = wholeConversion.length > 0 ? totalViewsCount / wholeConversion.length : 0;
// Średnia liczba sprzedanych sztuk
var totalAverageSoldItems = wholeConversion.length > 0 ? wholeConversion.reduce((sum, entry) => sum + entry.averageSoldItems, 0) : 0;
var averageSoldItems = wholeConversion.length > 0 ? totalAverageSoldItems / wholeConversion.length : 0;
//-----------------------------------------------//
// Filtrowanie danych w wholeConversion dla transactions > 0
var successfulConversions = wholeConversion.length > 0 ? wholeConversion.filter(entry => entry.transactions > 0) : [];
// Obliczenia na podstawie przefiltrowanych danych
var totalSuccessfulTransactions = successfulConversions.length > 0 ? successfulConversions.reduce((sum, entry) => sum + entry.transactions, 0) : 0;
var averageSuccessfulTransactions = successfulConversions.length > 0 ? totalSuccessfulTransactions / successfulConversions.length : 0;
var averageSuccessfulConversion = successfulConversions.length > 0 ? successfulConversions.reduce((sum, entry) => sum + entry.conversion, 0) / successfulConversions.length : 0;
var averageSuccessfulViewsCount = successfulConversions.length > 0 ? successfulConversions.reduce((sum, entry) => sum + entry.viewsCount, 0) / successfulConversions.length : 0;
var averageSuccessfulSoldItems = successfulConversions.length > 0 ? successfulConversions.reduce((sum, entry) => sum + entry.averageSoldItems, 0) / successfulConversions.length : 0;
var SccessfulViewsCount = successfulConversions.length > 0 ? successfulConversions.reduce((sum, entry) => sum + entry.viewsCount, 0) : 0;
const calculatePercentageDiff = (currentValue, referenceValue) => {
if (referenceValue === 0) return 0; // Unikaj dzielenia przez zero
return ((currentValue - referenceValue) / referenceValue * 100).toFixed(2);
};
const calculatePointsDifference = (currentPercentage, referencePercentage) => {
// Obliczamy różnicę w punktach procentowych
return (currentPercentage - referencePercentage).toFixed(2); // Zwracamy różnicę z dwoma miejscami po przecinku
};
function calculateDiffClass(value) {
return value >= 0 ? 'text-success' : 'text-danger'; // Zwraca klasę w zależności od wartości
}
const calculateProportionPercentage = (x, y) => {
if (x === 0) return 0; // Unikaj dzielenia przez zero
return ((y / x) * 100).toFixed(2); // Oblicza procent, jakim y jest względem x
};
const style = document.createElement('style');
style.innerHTML = `
.text-success {
color: green; /* Zielony kolor dla wartości dodatnich */
}
.text-danger {
color: red; /* Czerwony kolor dla wartości ujemnych */
}
`;
document.head.appendChild(style);
/* Tworzenie przycisku "Pobierz do Excela"
let downloadButton = document.createElement('button');
downloadButton.textContent = 'Pobierz do Excela';
downloadButton.style.position = 'fixed';
downloadButton.style.top = '50px';
downloadButton.style.right = '10px';
downloadButton.style.zIndex = '9999';
downloadButton.style.padding = '10px 20px';
downloadButton.style.backgroundColor = '#4CAF50';
downloadButton.style.color = 'white';
downloadButton.style.border = 'none';
downloadButton.style.borderRadius = '5px';
downloadButton.style.cursor = 'pointer';
document.body.appendChild(downloadButton);*/
// Wyświetlamy dane w modalnym oknie
let modalHTML = '';
// Sekcja danych sprzedażowych (tylko jeśli dostępne)
if (dataResults.salesData) {
modalHTML += `
<div style="border: 1px solid #ddd; border-radius: 8px; padding: 15px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 20px;" class="conversion">
<h3>📊 Wartość sprzedaży:</h3>
<p><strong>Sprzedaż w wybranym okresie:</strong> ${formatCurrency(totalSale)}</p>
<p><strong>Sprzedaż dzisiaj:</strong> ${formatCurrency(todaySale)}</p>
<p><strong>Sprzedaż wczoraj:</strong> ${formatCurrency(yesterdaySale)}</p>
<br>
<h4 class="section-title">Dodatkowe informacje</h4>
<p><strong>Tytuł:</strong> ${dataResults.salesData.result.itemName || 'Brak danych'} (${(dataResults.salesData.result.itemName || '').length} znaków)</p>
<p><strong>Data rozpoczęcia oferty:</strong> ${formatDaty(offerStartDate)}</p>
<p><strong>Początkowa ilość:</strong> ${startingQuantity}</p>
<p><strong>Obecna ilość:</strong> ${currentQuantity}</p>
<p><strong>Sprzedawca:</strong> ${seller}</p>
${filtProductId ? `<a class="product-btn" href="https://allegro.pl/moje-allegro/sprzedaz/produkt/zglos-blad/${filtProductId}" target="_blank">Sprawdź / zgłoś produkt</a>` : ''}
</div>
`;
} else {
modalHTML += `
<div style="border: 1px solid #ffa500; border-radius: 8px; padding: 15px; box-shadow: 0 2px 4px rgba(255,165,0,0.1); margin-bottom: 20px; background-color: #fff8e1;">
<h3 style="color: #f57c00;">⚠️ Dane sprzedaży niedostępne</h3>
<p>Nie udało się pobrać danych sprzedażowych dla tej oferty.</p>
</div>
`;
}
// Sekcja cykli promowania (tylko jeśli dostępne)
if (dataResults.salesData && promotionCycles.length > 0) {
modalHTML += `
<div style="border: 1px solid #ddd; border-radius: 8px; padding: 15px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 20px;">
<h3>🎯 Cykle promowania oferty:</h3>
${promotionCycles.map((cycle) => {
let promotionOptions;
switch (cycle.promotionOptions) {
case 4:
promotionOptions = 'Wyróżnienie ';
break;
case 68:
promotionOptions = 'Wyróżnienie + darmowa dostawa';
break;
default:
promotionOptions = 'Wyróżnienie';
}
return `<p><strong>${promotionOptions}</strong>: ${formatDate(cycle.startDate)} do ${formatDate(cycle.endDate)}</p>`;
}).join('')}
</div>
`;
}
// Sekcja potencjału wyróżnień (tylko jeśli dostępne)
if (dataResults.promotionData && entries.length > 0) {
modalHTML += `
<div style="border: 1px solid #ddd; border-radius: 8px; padding: 15px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 20px;">
<h3 style="margin-top: 0; color: #fff; background-color: #007bff; padding: 10px 15px; border-radius: 4px; margin: -15px -15px 15px -15px;">🎯 Potencjał wyróżnień dla tego produktu</h3>
<div class="prom-efficiency">
<table>
<thead>
<tr>
<th>Typ</th>
<th>Potencjał</th>
<th>Udział sprzedaży</th>
<th>Udział transakcji</th>
</tr>
</thead>
<tbody>
${Object.keys(FreeDeliveryAndPromotion).length > 0 ? `
<tr>
<td>Wyróżnienie + Darmowa dostawa</td>
<td>${FreeDeliveryAndPromotion.efficiencyClass}</td>
<td>${FreeDeliveryAndPromotion.sale}</td>
<td>${FreeDeliveryAndPromotion.transactions}</td>
</tr>
` : ''}
${Object.keys(FreeDelivery).length > 0 ? `
<tr>
<td>Darmowa dostawa</td>
<td>${FreeDelivery.efficiencyClass}</td>
<td>${FreeDelivery.sale}</td>
<td>${FreeDelivery.transactions}</td>
</tr>
` : ''}
${Object.keys(Promotion).length > 0 ? `
<tr>
<td>Wyróżnienie</td>
<td>${Promotion.efficiencyClass}</td>
<td>${Promotion.sale}</td>
<td>${Promotion.transactions}</td>
</tr>
` : ''}
</tbody>
</table>
</div>
</div>
`;
} else if (dataResults.promotionData === null) {
modalHTML += `
<div style="border: 1px solid #ffa500; border-radius: 8px; padding: 15px; box-shadow: 0 2px 4px rgba(255,165,0,0.1); margin-bottom: 20px; background-color: #fff8e1;">
<h3 style="color: #f57c00;">⚠️ Dane promocji niedostępne</h3>
<p>Nie udało się pobrać danych o potencjale wyróżnień dla tej oferty.</p>
</div>
`;
}
// Sekcja monitorowania cen (tylko jeśli dostępne)
if (dataResults.priceMonitorData && filteredEntries.length > 0) {
modalHTML += `
<div style="border: 1px solid #ddd; border-radius: 8px; padding: 15px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 20px;">
<h3>💰 Monitorowanie cen:</h3>
${filteredEntries.map(entry =>
`<div style="margin-bottom: 10px;">
<p>${formatDate(entry.priceChange)}: Stara cena: <strong>${formatCurrency(entry.oldPrice)}</strong> | Nowa cena: <strong>${formatCurrency(entry.newPrice)}</strong></p>
</div>`
).join('')}
<button id="chart-button" style="margin-top: 10px; padding: 10px 15px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer;">
Pokaż wykres
</button>
<canvas id="price-chart" style="display: none; margin-top: 15px;"></canvas>
</div>
`;
} else if (dataResults.priceMonitorData === null) {
modalHTML += `
<div style="border: 1px solid #ffa500; border-radius: 8px; padding: 15px; box-shadow: 0 2px 4px rgba(255,165,0,0.1); margin-bottom: 20px; background-color: #fff8e1;">
<h3 style="color: #f57c00;">⚠️ Dane monitorowania cen niedostępne</h3>
<p>Nie udało się pobrać danych o zmianach cen dla tej oferty.</p>
</div>
`;
} else {
modalHTML += `
<div style="border: 1px solid #ddd; border-radius: 8px; padding: 15px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 20px;">
<h3>💰 Monitorowanie cen:</h3>
<p>Brak danych o cenach dla tego ID.</p>
</div>
`;
}
// Sekcja słów kluczowych (tylko jeśli dostępne)
if (dataResults.keywordsData && dataResults.keywordsData.result && dataResults.keywordsData.result.entries) {
const keywords = dataResults.keywordsData.result.entries
.filter(entry => entry.saleParticipation && entry.saleParticipation.sale > 0.3)
.sort((a, b) => b.saleParticipation.sale - a.saleParticipation.sale);
if (keywords.length > 0) {
modalHTML += `
<div style="border: 1px solid #ddd; border-radius: 8px; padding: 15px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 20px;">
<h3>🔑 Słowa kluczowe</h3>
<div style="margin-bottom: 15px;">
<label style="margin-right: 15px;">
<input type="radio" name="keyword-filter" value="2" checked> 2+ znaki
</label>
<label>
<input type="radio" name="keyword-filter" value="3"> 3+ znaki
</label>
</div>
<div id="keywords-container">
<div style="display: flex; justify-content: space-between; align-items: center; padding: 8px 0; border-bottom: 2px solid #007bff; font-weight: bold; background-color: #f8f9fa;">
<span>Słowo kluczowe</span>
<span>Udział</span>
</div>
<div id="keywords-list">
${keywords.slice(0, 5).map(keyword => `
<div style="display: flex; justify-content: space-between; align-items: center; padding: 8px 0; border-bottom: 1px solid #eee;">
<span style="font-weight: bold;">${keyword.keyword}</span>
<span style="color: #007bff;">${(keyword.saleParticipation.sale * 100).toFixed(1)}%</span>
</div>
`).join('')}
${keywords.length > 5 ? `
<div id="all-keywords" style="display: none;">
${keywords.slice(5).map(keyword => `
<div style="display: flex; justify-content: space-between; align-items: center; padding: 8px 0; border-bottom: 1px solid #eee;">
<span style="font-weight: bold;">${keyword.keyword}</span>
<span style="color: #007bff;">${(keyword.saleParticipation.sale * 100).toFixed(1)}%</span>
</div>
`).join('')}
</div>
<button id="show-more-keywords" style="margin-top: 10px; padding: 8px 15px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer;">
Pokaż więcej (${keywords.length - 5} więcej)
</button>
` : ''}
</div>
</div>
</div>
`;
} else {
modalHTML += `
<div style="border: 1px solid #ddd; border-radius: 8px; padding: 15px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 20px;">
<h3>🔑 Słowa kluczowe</h3>
<p>Brak słów kluczowych z udziałem powyżej 30%.</p>
</div>
`;
}
} else if (dataResults.keywordsData === null) {
modalHTML += `
<div style="border: 1px solid #ffa500; border-radius: 8px; padding: 15px; box-shadow: 0 2px 4px rgba(255,165,0,0.1); margin-bottom: 20px; background-color: #fff8e1;">
<h3 style="color: #f57c00;">⚠️ Dane słów kluczowych niedostępne</h3>
<p>Nie udało się pobrać danych o słowach kluczowych dla tej oferty.</p>
</div>
`;
}
// Sekcja danych konwersji (tylko jeśli dostępne)
if (dataResults.conversionData && conversionData.length > 0) {
modalHTML += `
<div style="border: 1px solid #ddd; border-radius: 8px; padding: 15px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 20px;" class="conversion">
<h3>🔄 Dane konwersji:</h3>
${conversionData.map((data, index) => `
<div style="margin-bottom: 10px;" class="original-conversion">
<p>Transakcje: <strong>${data.transactions}</strong>
<span style="display: none;" class="transaction-diff ${calculateDiffClass(calculateProportionPercentage(totalTransactions, data.transactions))}">
(${calculateProportionPercentage(totalTransactions, data.transactions)}% wszystkich transakcji)
</span>
<span style="display: none;" class="successful-transaction-diff ${calculateDiffClass(calculateProportionPercentage(totalSuccessfulTransactions, data.transactions))}">
(${calculateProportionPercentage(totalSuccessfulTransactions, data.transactions)}% wszystkich transakcji)
</span>
</p>
<p>Średnia liczba sprzedanych sztuk: <strong>${data.averageSoldItems}</strong>
<span style="display: none;" class="average-sold-items-diff ${calculateDiffClass(calculatePercentageDiff(data.averageSoldItems, averageSoldItems.toFixed(2)))}">
(${calculatePercentageDiff(data.averageSoldItems, averageSoldItems.toFixed(2))}%)
</span>
<span style="display: none;" class="average-sold-items-successful-diff ${calculateDiffClass(calculatePercentageDiff(data.averageSoldItems, averageSuccessfulSoldItems.toFixed(2)))}">
(${calculatePercentageDiff(data.averageSoldItems, averageSuccessfulSoldItems.toFixed(2))}%)
</span>
</p>
<p>Liczba odsłon: <strong>${data.viewsCount}</strong>
<span style="display: none;" class="views-count-diff ${calculateDiffClass(calculatePercentageDiff(data.viewsCount, averageViewsCount.toFixed(2)))}">
(${calculateProportionPercentage(totalViewsCount.toFixed(2), data.viewsCount)}% wszystkich odsłon)
</span>
<span style="display: none;" class="views-count-successful-diff ${calculateDiffClass(calculatePercentageDiff(data.viewsCount, averageSuccessfulViewsCount.toFixed(2)))}">
(${calculateProportionPercentage(SccessfulViewsCount, data.viewsCount)}% wszystkich odsłon)
</span>
</p>
<p>Współczynnik konwersji: <strong>${(data.conversion * 100).toFixed(2)}%</strong>
<span style="display: none;" class="conversion-diff ${calculateDiffClass(calculatePointsDifference((data.conversion * 100).toFixed(2), (averageConversion * 100).toFixed(2)))}">
(${calculatePointsDifference((data.conversion * 100).toFixed(2), (averageConversion * 100).toFixed(2))} pkt %)
</span>
<span style="display: none;" class="successful-conversion-diff ${calculateDiffClass(calculatePointsDifference((data.conversion * 100).toFixed(2), (averageSuccessfulConversion * 100).toFixed(2)))}">
(${calculatePointsDifference((data.conversion * 100).toFixed(2), (averageSuccessfulConversion * 100).toFixed(2))} pkt %)
</span>
</p>
<p>Dodania do ulubionych: <strong>${data.favoriteAdditionsCount}</strong></p>
<p>Dodania do koszyka: <strong>${data.cartAdditionsCount}</strong></p>
<button class="show-total-stats" data-index="${index}" style="margin-top: 10px;">Pokaż całe statystyki</button>
<button class="show-successful-stats" data-index="${index}" style="margin-top: 10px;">Pokaż udane transakcje</button>
<div id="more-info-${index}" style="display: none; margin-top: 10px;">
<div class="total-stats" style="display: none;">
<h3>Statystyki ofert które miały wizyty</h3>
<p>Średnia liczba sprzedanych sztuk: <strong>${averageSoldItems.toFixed(2)}</strong></p>
<p>Wszystkich transakcji: <strong>${totalTransactions}</strong></p>
<p>Średnia liczba transakcji: <strong>${averageTransactions.toFixed(2)}</strong></p>
<p>Liczba wyświetleń: <strong>${totalViewsCount}</strong></p>
<p>Średnia konwersja: <strong>${(averageConversion * 100).toFixed(2)}%</strong></p>
<p>Średnia liczba odsłon: <strong>${averageViewsCount.toFixed(2)}</strong></p>
</div>
<div class="successful-stats" style="display: none;">
<h3>Statystyki ofert które miały transakcje</h3>
<p>Średnia liczba sprzedanych sztuk: <strong>${averageSuccessfulSoldItems.toFixed(2)}</strong></p>
<p>Wszystkich transakcji: <strong>${totalSuccessfulTransactions}</strong></p>
<p>Średnia liczba transakcji: <strong>${averageSuccessfulTransactions.toFixed(2)}</strong></p>
<p>Liczba wyświetleń: <strong>${SccessfulViewsCount}</strong></p>
<p>Średnia konwersja: <strong>${(averageSuccessfulConversion * 100).toFixed(2)}%</strong></p>
<p>Średnia liczba odsłon: <strong>${averageSuccessfulViewsCount.toFixed(2)}</strong></p>
</div>
</div>
</div>
`).join('')}
<button id="showEntriesBtn">Pokaż statystyki ofert</button>
</div>
`;
} else if (dataResults.conversionData === null) {
modalHTML += `
<div style="border: 1px solid #ffa500; border-radius: 8px; padding: 15px; box-shadow: 0 2px 4px rgba(255,165,0,0.1); margin-bottom: 20px; background-color: #fff8e1;">
<h3 style="color: #f57c00;">⚠️ Dane konwersji niedostępne</h3>
<p>Nie udało się pobrać danych konwersji dla tej oferty.</p>
</div>
`;
} else {
modalHTML += `
<div style="border: 1px solid #ddd; border-radius: 8px; padding: 15px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 20px;">
<h3>Dane konwersji:</h3>
<p>Brak danych konwersji.</p>
</div>
`;
}
// Wstawiamy HTML do modala (zachowując progress bar)
const progressBar = document.getElementById('fetch-progress');
const spinner = document.querySelector("#loading-spinner");
// Ukrywamy spinner
if (spinner) spinner.style.display = 'none';
// Aktualizujemy przycisk retry po zakończeniu wszystkich fetchów
updateRetryButton();
// Dodajemy dane po progress barze
if (progressBar) {
progressBar.insertAdjacentHTML('afterend', modalHTML);
} else {
// Fallback - jeśli progress bar nie istnieje, dodajemy wszystko
modalContent.innerHTML = modalHTML;
}
// Obsługa przycisków (tylko jeśli dane konwersji są dostępne)
if (conversionData.length > 0) {
conversionData.forEach((data, index) => {
// Wybierz przycisk do pokazywania całych statystyk
const totalStatsButton = document.querySelector(`.show-total-stats[data-index="${index}"]`);
const successfulStatsButton = document.querySelector(`.show-successful-stats[data-index="${index}"]`);
if (totalStatsButton && successfulStatsButton) {
totalStatsButton.addEventListener('click', function() {
const moreInfoDiv = document.getElementById(`more-info-${index}`);
moreInfoDiv.style.display = 'block';
moreInfoDiv.querySelector('.total-stats').style.display = 'block';
totalStatsButton.style.backgroundColor = '#ff5a00';
successfulStatsButton.style.backgroundColor = '#007bff';
moreInfoDiv.querySelector('.successful-stats').style.display = 'none'; // Ukryj udane statystyki
// Uzyskaj element <span> z różnicą
const transactionDiffSpan = document.querySelector(`.transaction-diff`);
const averageSoldItemsDiffSpan = document.querySelector(`.average-sold-items-diff`);
const viewsCountDiffSpan = document.querySelector(`.views-count-diff`);
const conversionDiffSpan = document.querySelector(`.conversion-diff`);
// Uczyń <span> widocznymi
if (transactionDiffSpan) transactionDiffSpan.style.display = 'inline';
if (averageSoldItemsDiffSpan) averageSoldItemsDiffSpan.style.display = 'inline';
if (viewsCountDiffSpan) viewsCountDiffSpan.style.display = 'inline';
if (conversionDiffSpan) conversionDiffSpan.style.display = 'inline';
// Dodajemy const dla drugich spanów
const successfulTransactionDiffSpan = document.querySelector(`.successful-transaction-diff`);
const averageSoldItemsSuccessfulDiffSpan = document.querySelector(`.average-sold-items-successful-diff`);
const viewsCountSuccessfulDiffSpan = document.querySelector(`.views-count-successful-diff`);
const successfulConversionDiffSpan = document.querySelector(`.successful-conversion-diff`);
if (successfulTransactionDiffSpan) successfulTransactionDiffSpan.style.display = 'none';
if (averageSoldItemsSuccessfulDiffSpan) averageSoldItemsSuccessfulDiffSpan.style.display = 'none';
if (viewsCountSuccessfulDiffSpan) viewsCountSuccessfulDiffSpan.style.display = 'none';
if (successfulConversionDiffSpan) successfulConversionDiffSpan.style.display = 'none';
});
successfulStatsButton.addEventListener('click', function() {
const moreInfoDiv = document.getElementById(`more-info-${index}`);
moreInfoDiv.style.display = 'block';
moreInfoDiv.querySelector('.total-stats').style.display = 'none'; // Ukryj całe statystyki
moreInfoDiv.querySelector('.successful-stats').style.display = 'block';
successfulStatsButton.style.backgroundColor = '#ff5a00';
totalStatsButton.style.backgroundColor = '#007bff';
// Uzyskaj element <span> z różnicą
const transactionDiffSpan = document.querySelector(`.transaction-diff`);
const averageSoldItemsDiffSpan = document.querySelector(`.average-sold-items-diff`);
const viewsCountDiffSpan = document.querySelector(`.views-count-diff`);
const conversionDiffSpan = document.querySelector(`.conversion-diff`);
// Uczyń <span> niewidocznymi
if (transactionDiffSpan) transactionDiffSpan.style.display = 'none';
if (averageSoldItemsDiffSpan) averageSoldItemsDiffSpan.style.display = 'none';
if (viewsCountDiffSpan) viewsCountDiffSpan.style.display = 'none';
if (conversionDiffSpan) conversionDiffSpan.style.display = 'none';
// Dodajemy const dla drugich spanów
const successfulTransactionDiffSpan = document.querySelector(`.successful-transaction-diff`);
const averageSoldItemsSuccessfulDiffSpan = document.querySelector(`.average-sold-items-successful-diff`);
const viewsCountSuccessfulDiffSpan = document.querySelector(`.views-count-successful-diff`);
const successfulConversionDiffSpan = document.querySelector(`.successful-conversion-diff`);
// Uczyń <span> widocznymi
if (successfulTransactionDiffSpan) successfulTransactionDiffSpan.style.display = 'inline';
if (averageSoldItemsSuccessfulDiffSpan) averageSoldItemsSuccessfulDiffSpan.style.display = 'inline';
if (viewsCountSuccessfulDiffSpan) viewsCountSuccessfulDiffSpan.style.display = 'inline';
if (successfulConversionDiffSpan) successfulConversionDiffSpan.style.display = 'inline';
});
}
});
}
// Generujemy wykres na podstawie dostępnych danych (tylko jeśli są dane)
if (filteredEntries.length > 0) {
generatePriceChart(filteredEntries);
// Dodajemy event listener do przycisku "Wykres"
const chartButton = document.querySelector("#chart-button");
if (chartButton) {
chartButton.addEventListener("click", function() {
const canvas = document.getElementById('price-chart');
if (canvas) {
// Zmieniamy widoczność canvasu
if (canvas.style.display === "none") {
canvas.style.display = "block"; // Pokaż wykres
} else {
canvas.style.display = "none"; // Ukryj wykres
}
}
});
}
}
// Dodajemy event listener do przycisku "Pokaż wszystkie wpisy"
const showEntriesBtn = document.querySelector("#showEntriesBtn");
if (showEntriesBtn && wholeConversion.length > 0) {
showEntriesBtn.addEventListener("click", function() {
openEntriesModal(wholeConversion); // Użyj danych z wholeConversion
});
}
// Dodajemy event listenery dla słów kluczowych
if (dataResults.keywordsData && dataResults.keywordsData.result && dataResults.keywordsData.result.entries) {
const keywords = dataResults.keywordsData.result.entries
.filter(entry => entry.saleParticipation && entry.saleParticipation.sale > 0.3)
.sort((a, b) => b.saleParticipation.sale - a.saleParticipation.sale);
// Event listener dla filtrów długości słów
const filterRadios = document.querySelectorAll('input[name="keyword-filter"]');
filterRadios.forEach(radio => {
radio.addEventListener('change', function() {
const minLength = parseInt(this.value);
const filteredKeywords = keywords.filter(keyword => keyword.keyword.length >= minLength);
const keywordsList = document.getElementById('keywords-list');
const allKeywords = document.getElementById('all-keywords');
const showMoreBtn = document.getElementById('show-more-keywords');
if (keywordsList) {
keywordsList.innerHTML = filteredKeywords.slice(0, 5).map(keyword => `
<div style="display: flex; justify-content: space-between; align-items: center; padding: 8px 0; border-bottom: 1px solid #eee;">
<span style="font-weight: bold;">${keyword.keyword}</span>
<span style="color: #007bff;">${(keyword.saleParticipation.sale * 100).toFixed(1)}%</span>
</div>
`).join('') + (filteredKeywords.length > 5 ? `
<div id="all-keywords" style="display: none;">
${filteredKeywords.slice(5).map(keyword => `
<div style="display: flex; justify-content: space-between; align-items: center; padding: 8px 0; border-bottom: 1px solid #eee;">
<span style="font-weight: bold;">${keyword.keyword}</span>
<span style="color: #007bff;">${(keyword.saleParticipation.sale * 100).toFixed(1)}%</span>
</div>
`).join('')}
</div>
<button id="show-more-keywords" style="margin-top: 10px; padding: 8px 15px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer;">
Pokaż więcej (${filteredKeywords.length - 5} więcej)
</button>
` : '');
}
});
});
// Event listener dla przycisku "Pokaż więcej" - delegacja zdarzeń
document.addEventListener('click', function(e) {
if (e.target && e.target.id === 'show-more-keywords') {
const allKeywords = document.getElementById('all-keywords');
if (allKeywords) {
if (allKeywords.style.display === 'none') {
allKeywords.style.display = 'block';
e.target.textContent = 'Pokaż mniej';
} else {
allKeywords.style.display = 'none';
e.target.textContent = `Pokaż więcej (${keywords.length - 5} więcej)`;
}
}
}
});
}
// Dodajemy event listener do przycisku retry
const retryButton = document.getElementById('retry-failed-fetches');
if (retryButton) {
retryButton.addEventListener('click', async function() {
retryButton.disabled = true;
retryButton.textContent = '🔄 Ponawianie...';
// Ponów tylko nieudane fetche
for (const fetchName of dataResults.failedFetches) {
await retryFailedFetch(fetchName, auctionID, title, startDate, endDate);
}
// Po udanym retry, odśwież całą zawartość modala
if (dataResults.failedFetches.length === 0) {
// Odśwież zawartość modala z aktualnymi danymi
refreshModalContent(auctionID, title, startDate, endDate);
}
retryButton.disabled = false;
updateRetryButton();
});
}
// Funkcja do odświeżania zawartości modala po retry
function refreshModalContent(auctionID, title, startDate, endDate) {
// Usuń starą zawartość modala (zachowując progress bar)
const progressBar = document.getElementById('fetch-progress');
if (progressBar) {
// Usuń wszystkie elementy po progress barze
let nextElement = progressBar.nextElementSibling;
while (nextElement) {
const toRemove = nextElement;
nextElement = nextElement.nextElementSibling;
toRemove.remove();
}
}
// Wywołaj ponownie generowanie zawartości modala
generateModalContent(auctionID, title, startDate, endDate);
}
// Funkcja do generowania zawartości modala (uproszczona - tylko odświeża sekcję danych sprzedaży)
function generateModalContent(auctionID, title, startDate, endDate) {
// Wyodrębniamy dane sprzedaży
var totalSale = 0, todaySale = 0, yesterdaySale = 0;
var offerStartDate = '', startingQuantity = 0, currentQuantity = 0, seller = '';
var productid = [], filtProductId = null;
if (dataResults.salesData) {
const salesData = dataResults.salesData.result;
totalSale = salesData.saleInfo?.totalSale || 0;
todaySale = salesData.saleInfo?.todaySale || 0;
yesterdaySale = salesData.saleInfo?.yesterdaySale || 0;
offerStartDate = salesData.overallItemInfo?.startDate || '';
startingQuantity = salesData.startingQuantity || 0;
currentQuantity = salesData.currentQuantity || 0;
seller = salesData.seller?.name || '';
productid = salesData.pmdProductIds || [];
if (productid.length > 0) filtProductId = productid[0];
}
// Generujemy tylko sekcję danych sprzedaży
let salesHTML = '';
if (dataResults.salesData) {
salesHTML = `
<div style="border: 1px solid #ddd; border-radius: 8px; padding: 15px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 20px;" class="conversion">
<h3>Wartość sprzedaży:</h3>
<p><strong>Sprzedaż w wybranym okresie:</strong> ${formatCurrency(totalSale)}</p>
<p><strong>Sprzedaż dzisiaj:</strong> ${formatCurrency(todaySale)}</p>
<p><strong>Sprzedaż wczoraj:</strong> ${formatCurrency(yesterdaySale)}</p>
<br>
<h4 class="section-title">Dodatkowe informacje</h4>
<p><strong>Tytuł:</strong> ${dataResults.salesData.result.itemName || 'Brak danych'} (${(dataResults.salesData.result.itemName || '').length} znaków)</p>
<p><strong>Data rozpoczęcia oferty:</strong> ${formatDaty(offerStartDate)}</p>
<p><strong>Początkowa ilość:</strong> ${startingQuantity}</p>
<p><strong>Obecna ilość:</strong> ${currentQuantity}</p>
<p><strong>Sprzedawca:</strong> ${seller}</p>
${filtProductId ? `<a class="product-btn" href="https://allegro.pl/moje-allegro/sprzedaz/produkt/zglos-blad/${filtProductId}" target="_blank">Sprawdź / zgłoś produkt</a>` : ''}
</div>
`;
} else {
salesHTML = `
<div style="border: 1px solid #ffa500; border-radius: 8px; padding: 15px; box-shadow: 0 2px 4px rgba(255,165,0,0.1); margin-bottom: 20px; background-color: #fff8e1;">
<h3 style="color: #f57c00;">⚠️ Dane sprzedaży niedostępne</h3>
<p>Nie udało się pobrać danych sprzedażowych dla tej oferty.</p>
</div>
`;
}
// Znajdź i zastąp sekcję danych sprzedaży
const progressBar = document.getElementById('fetch-progress');
if (progressBar) {
// Znajdź pierwszą sekcję po progress barze (powinna być sekcją danych sprzedaży)
const firstSection = progressBar.nextElementSibling;
if (firstSection && firstSection.classList.contains('conversion')) {
firstSection.outerHTML = salesHTML;
} else {
// Jeśli nie ma sekcji danych sprzedaży, dodaj ją
progressBar.insertAdjacentHTML('afterend', salesHTML);
}
}
}
// Funkcja do ponawiania nieudanych fetchów
async function retryFailedFetch(fetchName, auctionID, title, startDate, endDate) {
updateFetchStatus(fetchName, 'loading', 'Ponawianie...');
try {
switch (fetchName) {
case 'deals':
await retryDealsFetch(auctionID, startDate, endDate);
break;
case 'prices':
await retryPricesFetch(title, startDate, endDate);
break;
case 'conversion':
await retryConversionFetch(auctionID, startDate, endDate);
break;
case 'product':
await retryProductFetch(auctionID, startDate, endDate);
break;
case 'promotion':
await retryPromotionFetch(startDate, endDate);
break;
case 'keywords':
await retryKeywordsFetch(auctionID, startDate, endDate);
break;
}
} catch (error) {
updateFetchStatus(fetchName, 'error', `Błąd: ${error.message}`);
console.error(`Błąd ponawiania ${fetchName}:`, error);
}
}
// Funkcje retry dla każdego typu fetcha
async function retryDealsFetch(auctionID, startDate, endDate) {
const response = await fetch("https://edge.allegro.pl/analytics/auction/deals", {
"credentials": "include",
"headers": {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0",
"Accept": "application/vnd.allegro.internal.v1+json",
"Accept-Language": "pl-PL",
"Content-Type": "application/vnd.allegro.internal.v1+json",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-site",
"Priority": "u=0",
"Pragma": "no-cache",
"Cache-Control": "no-cache"
},
"referrer": "https://allegro.pl/",
"body": JSON.stringify({
"includePhrasesMode": "OfferOrProduct",
"excludePhrasesMode": "OfferOrProduct",
"auctionId": auctionID,
"sellerIds": [],
"marketplaceIds": ["allegro-pl"],
"startDate": startDate,
"endDate": endDate,
"startDateTime": startDate + "T00:00:00.000Z",
"endDateTime": endDate + "T23:59:59.999Z",
"reportCode": "TOP7D"
}),
"method": "POST",
"mode": "cors"
});
if (response.ok) {
const data = await response.json();
if (data && data.result && data.result.saleInfo) {
dataResults.salesData = data;
updateFetchStatus('deals', 'success', 'Pobrano pomyślnie');
} else {
updateFetchStatus('deals', 'error', 'Nieprawidłowa struktura danych');
}
} else {
updateFetchStatus('deals', 'error', `Błąd HTTP: ${response.status}`);
}
}
async function retryPricesFetch(title, startDate, endDate) {
const response = await fetch("https://edge.allegro.pl/analytics/reports/auctions/pricemonitor/pricejournal", {
"credentials": "include",
"headers": {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0",
"Accept": "application/vnd.allegro.internal.v1+json",
"Accept-Language": "pl-PL",
"Content-Type": "application/vnd.allegro.internal.v1+json",
"Priority": "u=4",
"Pragma": "no-cache",
"Cache-Control": "no-cache",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-site"
},
"referrer": "https://allegro.pl/",
"body": JSON.stringify({
"includePhrases": title,
"includePhrasesMode": "OfferOrProduct",
"excludePhrasesMode": "OfferOrProduct",
"auctionType": 1,
"sellerIds": [],
"skip": 0,
"take": 1000,
"marketplaceIds": ["allegro-pl"],
"startDate": startDate + "T00:00:00.000Z",
"endDate": endDate + "T00:00:00.000Z",
"startDateTime": startDate + "T00:00:00.000Z",
"endDateTime": endDate + "T23:59:59.999Z",
"reportCode": "TOP7D"
}),
"method": "POST",
"mode": "cors"
});
if (response.ok) {
const data = await response.json();
if (data && data.entries) {
dataResults.priceMonitorData = data;
updateFetchStatus('prices', 'success', 'Pobrano pomyślnie');
} else {
updateFetchStatus('prices', 'error', 'Nieprawidłowa struktura danych');
}
} else {
updateFetchStatus('prices', 'error', `Błąd HTTP: ${response.status}`);
}
}
async function retryConversionFetch(auctionID, startDate, endDate) {
if (!dataResults.salesData || !dataResults.salesData.result.pmdProductIds || dataResults.salesData.result.pmdProductIds.length === 0) {
updateFetchStatus('conversion', 'skipped', 'Brak productid');
return;
}
const productid = dataResults.salesData.result.pmdProductIds[0];
const response = await fetch("https://edge.allegro.pl/analytics/reports/sale/byconversion", {
"credentials": "include",
"headers": {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:130.0) Gecko/20100101 Firefox/130.0",
"Accept": "application/vnd.allegro.internal.v1+json",
"Accept-Language": "pl-PL",
"Content-Type": "application/vnd.allegro.internal.v1+json",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-site",
"Priority": "u=0",
"Pragma": "no-cache",
"Cache-Control": "no-cache"
},
"referrer": "https://allegro.pl/",
"body": JSON.stringify({
"includePhrases": productid,
"includePhrasesMode": "AnyId",
"excludePhrasesMode": "AnyId",
"sellerIds": [],
"includeCancelledAndReturned": true,
"skip": 0,
"take": 1000,
"marketplaceIds": ["allegro-pl"],
"startDate": startDate + "T00:00:00.000Z",
"endDate": endDate + "T00:00:00.000Z",
"startDateTime": startDate + "T00:00:00.000Z",
"endDateTime": endDate + "T23:59:59.999Z",
"reportCode": "TOP7D"
}),
"method": "POST",
"mode": "cors"
});
if (response.ok) {
const data = await response.json();
if (data && data.result && data.result.entries) {
dataResults.conversionData = data;
updateFetchStatus('conversion', 'success', 'Pobrano pomyślnie');
} else {
updateFetchStatus('conversion', 'error', 'Nieprawidłowa struktura danych');
}
} else {
updateFetchStatus('conversion', 'error', `Błąd HTTP: ${response.status}`);
}
}
async function retryProductFetch(auctionID, startDate, endDate) {
if (!dataResults.conversionData || !dataResults.conversionData.result || !dataResults.conversionData.result.entries) {
updateFetchStatus('product', 'skipped', 'Brak danych konwersji');
return;
}
const conversionDataAfter = dataResults.conversionData.result.entries.filter(entry => entry.ids.includes(auctionID));
const pmdProductName = conversionDataAfter.length ? conversionDataAfter[0].pmdProductName : null;
if (!pmdProductName) {
updateFetchStatus('product', 'skipped', 'Brak pmdProductName');
return;
}
const response = await fetch("https://edge.allegro.pl/analytics/reports/sale/byconversion", {
"credentials": "include",
"headers": {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:130.0) Gecko/20100101 Firefox/130.0",
"Accept": "application/vnd.allegro.internal.v1+json",
"Accept-Language": "pl-PL",
"Content-Type": "application/vnd.allegro.internal.v1+json",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-site"
},
"referrer": "https://allegro.pl/",
"body": JSON.stringify({
"includePhrases": pmdProductName,
"includePhrasesMode": "Product",
"excludePhrasesMode": "Product",
"auctionType": 1,
"sellerIds": [],
"skip": 0,
"take": 1000,
"marketplaceIds": ["allegro-pl"],
"startDate": startDate + "T00:00:00.000Z",
"endDate": endDate + "T00:00:00.000Z",
"startDateTime": startDate + "T00:00:00.000Z",
"endDateTime": endDate + "T23:59:59.999Z",
"reportCode": "TOP7D"
}),
"method": "POST",
"mode": "cors"
});
if (response.ok) {
const data = await response.json();
if (data && data.result) {
dataResults.conversionDataExtended = data;
updateFetchStatus('product', 'success', 'Pobrano pomyślnie');
} else {
updateFetchStatus('product', 'error', 'Nieprawidłowa struktura danych');
}
} else {
updateFetchStatus('product', 'error', `Błąd HTTP: ${response.status}`);
}
}
async function retryPromotionFetch(startDate, endDate) {
const response = await fetch("https://edge.allegro.pl/analytics/reports/sale/bypromotion", {
"credentials": "include",
"headers": {
"Accept": "application/vnd.allegro.internal.v1+json",
"Accept-Language": "pl-PL",
"Content-Type": "application/vnd.allegro.internal.v1+json",
"Priority": "u=1, i",
"Sec-CH-UA": "\"Google Chrome\";v=\"129\", \"Not=A?Brand\";v=\"8\", \"Chromium\";v=\"129\"",
"Sec-CH-UA-Mobile": "?0",
"Sec-CH-UA-Platform": "\"Windows\"",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-site"
},
"referrer": "https://allegro.pl/",
"referrerPolicy": "strict-origin-when-cross-origin",
"body": JSON.stringify({
"includePhrases": "2c3f3821-4408-42b8-9ce3-94103d84715e",
"includePhrasesMode": "AnyId",
"sellerIds": [],
"marketplaceIds": ["allegro-pl"],
"startDate": startDate + "T00:00:00.000Z",
"endDate": endDate + "T00:00:00.000Z",
"startDateTime": startDate + "T02:00:00.000Z",
"endDateTime": endDate + "T23:53:00.000Z",
"reportCode": "LASTFULL7DAYS"
}),
"method": "POST",
"mode": "cors"
});
if (response.ok) {
const data = await response.json();
if (data && data.result && data.result.entries) {
dataResults.promotionData = data;
updateFetchStatus('promotion', 'success', 'Pobrano pomyślnie');
} else {
updateFetchStatus('promotion', 'error', 'Nieprawidłowa struktura danych');
}
} else {
updateFetchStatus('promotion', 'error', `Błąd HTTP: ${response.status}`);
}
}
async function retryKeywordsFetch(auctionID, startDate, endDate) {
if (!dataResults.salesData || !dataResults.salesData.result.pmdProductIds || dataResults.salesData.result.pmdProductIds.length === 0) {
updateFetchStatus('keywords', 'skipped', 'Niemożliwe');
return;
}
const productId = dataResults.salesData.result.pmdProductIds[0];
const response = await fetch("https://edge.allegro.pl/analytics/reports/sale/bykeyword", {
"credentials": "include",
"headers": {
"Accept": "application/vnd.allegro.internal.v1+json",
"Accept-Language": "pl-PL",
"Content-Type": "application/vnd.allegro.internal.v1+json",
"Priority": "u=1, i",
"Sec-CH-UA": "\"Chromium\";v=\"140\", \"Not=A?Brand\";v=\"24\", \"Google Chrome\";v=\"140\"",
"Sec-CH-UA-Mobile": "?0",
"Sec-CH-UA-Platform": "\"Windows\"",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-site"
},
"referrer": "https://allegro.pl/",
"body": JSON.stringify({
"includePhrases": productId,
"includePhrasesMode": "AnyId",
"excludePhrasesMode": "OfferOrProduct",
"auctionId": auctionID,
"skip": 0,
"take": 1000,
"marketplaceIds": ["allegro-pl"],
"startDate": startDate + "T00:00:00.000Z",
"endDate": endDate + "T00:00:00.000Z",
"startDateTime": startDate + "T02:00:00.000Z",
"endDateTime": endDate + "T04:38:00.000Z",
"reportCode": "LASTFULL7DAYS"
}),
"method": "POST",
"mode": "cors"
});
if (response.ok) {
const data = await response.json();
if (data && data.result && data.result.entries) {
dataResults.keywordsData = data;
updateFetchStatus('keywords', 'success', 'Pobrano pomyślnie');
} else {
updateFetchStatus('keywords', 'error', 'Nieprawidłowa struktura danych');
}
} else {
updateFetchStatus('keywords', 'error', `Błąd HTTP: ${response.status}`);
}
}
// Funkcja do otwierania modalnego okna z tabelą
function openEntriesModal(entries) {
// Sprawdzenie, czy entries są dostępne
if (!entries || entries.length === 0) {
alert("Brak danych do wyświetlenia.");
return;
}
// Tworzenie zawartości tabeli
const tableRows = entries.map(entry => `
<tr>
<td style="text-align: center;">
<a href="https://allegro.pl/oferta/${entry.ids.join(', ')}" target="_blank">${entry.ids.join(', ')}</a>
</td>
<td style="text-align: center;">
<a href="https://allegro.pl/uzytkownik/${entry.seller.name}" target="_blank">${entry.seller.name}</a>
</td>
<td style="text-align: center;">${entry.transactions}</td>
<td style="text-align: center;">${entry.averageSoldItems}</td>
<td style="text-align: center;">${entry.viewsCount}</td>
<td style="text-align: center;">${(entry.conversion * 100).toFixed(2)}%</td>
<td style="text-align: center;">${entry.offerPrice.toFixed(2)} zł</td>
<td style="text-align: center;">${entry.favoriteAdditionsCount}</td>
<td style="text-align: center;">${entry.cartAdditionsCount}</td>
</tr>
`).join('');
// Tworzenie modalnego okna
const modalEntriesContent = `
<div id="modal-entries" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.8); display: flex; justify-content: center; align-items: center; z-index: 10001;max-width:100%">
<div style="background: white; padding: 30px; border-radius: 8px; width: 90%; max-width: 100%; overflow-y: auto; max-height: 80%; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);">
<div style="display: flex; justify-content: space-between; align-items: center;">
<h2 style="margin-top: 0; text-align: center; color: #333;">Wszystkie oferty</h2>
<button id="closeEntriesModalBtn" style="padding: 10px 15px; background-color: #dc3545; color: white; border: none; border-radius: 5px; cursor: pointer;">Zamknij</button>
</div>
<table style="width: 100%; border-collapse: collapse; margin-top: 20px; font-family: Arial, sans-serif;">
<thead style="background-color: #007bff; color: white;">
<tr>
<th style="padding: 15px; text-align: center;">Oferty</th>
<th style="padding: 15px; text-align: center;">Sprzedawca</th>
<th style="padding: 15px; text-align: center;">Transakcji <span class="filter-icon" data-col="2">🔽</span></th>
<th style="padding: 15px; text-align: center;">Śr. sprzedanych sztuk <span class="filter-icon" data-col="3">🔽</span></th>
<th style="padding: 15px; text-align: center;">Odsłon <span class="filter-icon" data-col="4">🔽</span></th>
<th style="padding: 15px; text-align: center;">Konwersja <span class="filter-icon" data-col="5">🔽</span></th>
<th style="padding: 15px; text-align: center;">Cena <span class="filter-icon" data-col="6">🔽</span></th>
<th style="padding: 15px; text-align: center;">Dodanych do ulubionych <span class="filter-icon" data-col="7">🔽</span></th>
<th style="padding: 15px; text-align: center;">Dodanych do koszyka <span class="filter-icon" data-col="8">🔽</span></th>
</tr>
</thead>
<tbody id="entriesTableBody">
${tableRows}
</tbody>
</table>
</div>
</div>
`;
// Wstawiamy modal do body
document.body.insertAdjacentHTML('beforeend', modalEntriesContent);
// Dodajemy style do wierszy tabeli
const style = document.createElement('style');
style.textContent = `
table {
border-radius: 8px;
overflow: hidden;
}
tbody tr:nth-child(even) {
background-color: #f2f2f2; /* Jasnoszare tło dla parzystych wierszy */
}
tbody tr:hover {
background-color: #e0e0e0; /* Jasnoszare tło przy najechaniu */
}
th, td {
padding: 12px; /* Większe odstępy w komórkach */
border-bottom: 1px solid #ddd; /* Dolna granica */
text-align: center; /* Wyśrodkowanie tekstu */
}
th {
font-weight: bold; /* Pogrubione nagłówki */
}
.filter-icon {
cursor: pointer;
font-size: 14px;
margin-left: 5px;
vertical-align: middle; /* Wyrównanie strzałki w pionie */
}
.ascending::before {
content: '🔼'; /* Strzałka w górę */
}
.descending::before {
content: '🔽'; /* Strzałka w dół */
}
`;
document.head.appendChild(style);
// Obsługa zamykania modalnego okna z tabelą
document.getElementById('closeEntriesModalBtn').addEventListener('click', () => {
const modal = document.getElementById('modal-entries');
if (modal) {
modal.remove(); // Usuwamy modal
}
});
// Obsługa filtrowania/sortowania
const filterIcons = document.querySelectorAll('.filter-icon');
filterIcons.forEach(icon => {
icon.addEventListener('click', () => {
const colIndex = parseInt(icon.getAttribute('data-col'));
const ascending = icon.classList.toggle('ascending'); // Ustalanie kierunku sortowania
// Resetowanie kierunku sortowania dla pozostałych kolumn
filterIcons.forEach(otherIcon => {
if (otherIcon !== icon) {
otherIcon.classList.remove('ascending', 'descending');
}
});
// Ustawianie kierunku sortowania
if (ascending) {
icon.classList.add('ascending');
} else {
icon.classList.add('descending');
}
const sortedEntries = [...entries].sort((a, b) => {
let aValue, bValue;
switch (colIndex) {
case 2: // Transakcji
aValue = a.transactions;
bValue = b.transactions;
break;
case 3: // Śr. sprzedanych sztuk
aValue = a.averageSoldItems;
bValue = b.averageSoldItems;
break;
case 4: // Odsłon
aValue = a.viewsCount;
bValue = b.viewsCount;
break;
case 5: // Konwersja
aValue = a.conversion;
bValue = b.conversion;
break;
case 6: // Cena
aValue = a.offerPrice;
bValue = b.offerPrice;
break;
case 7: // Dodanych do ulubionych
aValue = a.favoriteAdditionsCount;
bValue = b.favoriteAdditionsCount;
break;
case 8: // Dodanych do koszyka
aValue = a.cartAdditionsCount;
bValue = b.cartAdditionsCount;
break;
default:
return 0; // Nie sortuj, jeśli kolumna nie jest obsługiwana
}
return ascending ? (aValue - bValue) : (bValue - aValue);
});
// Uaktualniamy tabelę
const newTableRows = sortedEntries.map(entry => `
<tr>
<td style="text-align: center;">
<a href="https://allegro.pl/oferta/${entry.ids.join(', ')}" target="_blank">${entry.ids.join(', ')}</a>
</td>
<td style="text-align: center;">
<a href="https://allegro.pl/uzytkownik/${entry.seller.name}" target="_blank">${entry.seller.name}</a>
</td>
<td style="text-align: center;">${entry.transactions}</td>
<td style="text-align: center;">${entry.averageSoldItems}</td>
<td style="text-align: center;">${entry.viewsCount}</td>
<td style="text-align: center;">${(entry.conversion * 100).toFixed(2)}%</td>
<td style="text-align: center;">${entry.offerPrice.toFixed(2)} zł</td>
<td style="text-align: center;">${entry.favoriteAdditionsCount}</td>
<td style="text-align: center;">${entry.cartAdditionsCount}</td>
</tr>
`).join('');
document.getElementById('entriesTableBody').innerHTML = newTableRows;
});
});
}
} catch (error) {
console.error("Wystąpił błąd podczas pobierania danych:", error);
// Sprawdź czy wystąpił błąd 401
const has401Error = dataResults.errors.some(errorMsg => errorMsg.includes('401'));
const errorMessage = has401Error ?
'Brak dostępu, zaloguj się na właściwe konto Analytics' :
error.message;
const errorDescription = has401Error ?
'Zaloguj się na konto z dostępem do Analytics i spróbuj ponownie.' :
'Sprawdź połączenie internetowe i spróbuj ponownie.';
modalContent.innerHTML = `
<h2 style="margin-top: 0;">Statystyki dla oferty ${auctionID}</h2>
<div style="border: 1px solid #ff6b6b; border-radius: 8px; padding: 15px; box-shadow: 0 2px 4px rgba(255,107,107,0.1); margin-bottom: 20px; background-color: #ffe0e0;">
<h3 style="color: #d63031; margin-top: 0;">❌ Błąd krytyczny</h3>
<p style="color: #d63031;">Wystąpił błąd podczas pobierania danych: ${errorMessage}</p>
<p>${errorDescription}</p>
</div>
`;
} finally {
// Spinner jest już ukrywany w głównej części funkcji
}
}
function formatCurrency(amount) {
return new Intl.NumberFormat('pl-PL', { style: 'currency', currency: 'PLN' }).format(amount);
}
// Funkcja do generowania wykresu na podstawie zmian cen
// Funkcja do generowania wykresu na podstawie zmian cen
// Funkcja do generowania wykresu na podstawie zmian cen
// Funkcja do generowania wykresu na podstawie zmian cen
function generatePriceChart(priceEntries) {
console.log("Funkcja generatePriceChart została wywołana.");
console.log("Dane wejściowe:", priceEntries);
var labels, data;
var maxY; // Zmienna dla maksymalnej wartości osi Y
if (priceEntries.length > 0) {
// Jeśli są dane, używamy ich do generowania wykresu
labels = priceEntries.map(entry => formatDate(entry.priceChange));
data = priceEntries.map(entry => entry.newPrice);
// Obliczamy maksymalną wartość dla osi Y
const maxPrice = Math.max(...data);
maxY = Math.ceil(maxPrice * 1.2); // Ustawiamy maksymalną wartość na 20% wyższą
} else {
// Jeśli nie ma danych, używamy przykładowych danych
labels = ['No data'];
data = [0, 1];
maxY = 2; // Ustawiamy maksymalną wartość na 2 w przypadku braku danych
}
console.log("Etykiety:", labels);
console.log("Dane:", data);
console.log("Maksymalna wartość osi Y:", maxY); // Debugging
var canvas = document.getElementById('price-chart');
var ctx = canvas.getContext('2d');
// Ustawienie wymiarów canvas
canvas.width = 400;
canvas.height = 200;
// Inicjalizujemy wykres
new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [{
label: 'Cena',
data: data,
backgroundColor: 'rgba(75, 192, 192, 0.2)',
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 1,
pointRadius: 5,
pointHoverRadius: 8
}]
},
options: {
scales: {
y: {
beginAtZero: true,
max: maxY // Ustawiamy maksymalną wartość osi Y
}
},
plugins: {
tooltip: {
padding: 10, // Powiększa padding wewnętrzny
backgroundColor: 'rgba(0, 0, 0, 0.7)', // Tło tooltipa
titleColor: '#fff', // Kolor tytułu tooltipa
bodyColor: '#fff', // Kolor ciała tooltipa
titleFont: {
size: 16, // Zwiększamy rozmiar czcionki tytułu tooltipa
},
bodyFont: {
size: 14, // Zwiększamy rozmiar czcionki ciała tooltipa
},
callbacks: {
label: function(tooltipItem) {
const currentPrice = tooltipItem.raw; // Cena bieżącego punktu
const index = tooltipItem.dataIndex; // Indeks bieżącego punktu
// Obliczanie procentowej zmiany w stosunku do poprzedniego punktu
let percentChange = 0;
if (index > 0) { // Jeśli to nie pierwszy punkt
const previousPrice = data[index - 1];
percentChange = ((currentPrice - previousPrice) / previousPrice) * 100;
}
return [
`Cena: ${currentPrice.toFixed(2)} zł`,
`Zmiana: ${percentChange.toFixed(2)}%`
];
}
}
}
}
}
});
}
// Funkcja do formatowania wartości w złotówkach
function formatCurrency(value) {
return new Intl.NumberFormat('pl-PL', { style: 'currency', currency: 'PLN' }).format(value);
}
// Funkcja do formatowania dat w formacie dd.mm
function formatDate(dateString) {
var date = new Date(dateString);
var day = String(date.getDate()).padStart(2, '0');
var month = String(date.getMonth() + 1).padStart(2, '0');
return `${day}.${month}`;
}
function formatDaty(isoDateString) {
var parsedDate = new Date(isoDateString);
var formattedDay = String(parsedDate.getDate()).padStart(2, '0');
var formattedMonth = String(parsedDate.getMonth() + 1).padStart(2, '0');
var formattedYear = parsedDate.getFullYear(); // Wyciąganie roku
var formattedHours = String(parsedDate.getHours()).padStart(2, '0');
var formattedMinutes = String(parsedDate.getMinutes()).padStart(2, '0');
// Zwrócenie daty w formacie: dd-mm-rrrr | hh:mm
return `${formattedDay}-${formattedMonth}-${formattedYear} | ${formattedHours}:${formattedMinutes}`;
}
// Funkcja do obliczania dat
function getDateString(date) {
return date.toISOString().split('T')[0]; // Tylko data w formacie YYYY-MM-DD
}
// Funkcja do ustawienia interwału dodawania przycisków
function setupButtonAdditionInterval() {
// Dodaj przyciski co 1 sekundę
setInterval(addStatystykaButton, 1000);
}
// Czekamy, aż dokument zostanie w pełni załadowany
window.addEventListener('load', function() {
addStatystykaButton(); // Początkowe dodanie przycisków
setupButtonAdditionInterval(); // Ustawienie interwału do dodawania przycisków
// Dodajemy obserwatora MutationObserver z debounce dla wydajności
let updateTimeout;
const observer = new MutationObserver(function(mutations) {
// Sprawdź czy są rzeczywiście nowe artykuły
let hasNewArticles = false;
mutations.forEach(function(mutation) {
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
for (let node of mutation.addedNodes) {
if (node.nodeType === 1 && (node.matches && (node.matches('article') || node.matches('li') || node.querySelector('article')))) {
hasNewArticles = true;
break;
}
}
}
});
if (hasNewArticles) {
// Debounce - czekaj 300ms przed aktualizacją
clearTimeout(updateTimeout);
updateTimeout = setTimeout(() => {
addStatystykaButton();
}, 300);
}
});
// Obserwujemy tylko konkretne kontenery, nie całe body
const rightItemsDiv = document.querySelector("div[data-role='rightItems']");
if (rightItemsDiv) {
observer.observe(rightItemsDiv, {
childList: true,
subtree: true,
attributes: false,
characterData: false
});
}
// Obserwuj też główny kontener z ofertami (jeśli istnieje)
const mainContainer = document.querySelector("main, [data-role='main'], .opbox-listing");
if (mainContainer) {
observer.observe(mainContainer, {
childList: true,
subtree: true,
attributes: false,
characterData: false
});
}
});
})();
// --- Allegro Image Downloader ---
(function() {
'use strict';
'use strict';
if (window.vSprintPlusSettings && window.vSprintPlusSettings.img === false) return;
// --- KOPIOWANIE OPISU ---
function extractCleanText(container) {
const allowedTags = ['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'OL', 'LI', 'STRONG', 'EM', 'SPAN'];
let result = '';
function walk(node) {
if (allowedTags.includes(node.tagName)) {
const text = node.innerText.trim();
if (text) {
result += text + '\n';
}
}
for (let child of node.children) {
walk(child);
}
}
walk(container);
return result.trim();
}
function addCopyButton() {
const descBox = document.querySelector('div[data-box-name="Description card"]');
if (!descBox) return;
if (document.getElementById('kopiuj-opis-btn')) return;
const button = document.createElement('button');
button.id = 'kopiuj-opis-btn';
button.innerText = '📋 Kopiuj opis';
button.style.position = "relative";
button.style.zIndex = "9999";
button.style.marginBottom = "10px";
button.style.padding = "10px 20px";
button.style.backgroundColor = "#ff5a00";
button.style.color = "#fff";
button.style.border = "none";
button.style.cursor = "pointer";
button.style.fontSize = "16px";
button.style.borderRadius = "5px";
button.addEventListener('click', () => {
const text = extractCleanText(descBox);
if (text) {
GM_setClipboard(text);
// Pokaż krótką informację przy przycisku
const originalText = button.innerText;
button.innerText = '✅ Skopiowano!';
button.style.backgroundColor = '#28a745';
setTimeout(() => {
button.innerText = originalText;
button.style.backgroundColor = '#ff5a00';
}, 2000);
} else {
// Pokaż błąd przy przycisku
const originalText = button.innerText;
button.innerText = '❌ Brak tekstu';
button.style.backgroundColor = '#dc3545';
setTimeout(() => {
button.innerText = originalText;
button.style.backgroundColor = '#ff5a00';
}, 2000);
}
});
descBox.parentElement.insertBefore(button, descBox);
}
window.addEventListener('load', () => {
setTimeout(addCopyButton, 2000);
});
// --- POBIERANIE ZDJĘĆ ---
function tuTworzymyButton() {
let button = document.createElement("button");
button.innerText = "Pobierz zdjęcia";
button.style.position = "relative";
button.style.zIndex = "9999";
button.style.marginTop = "10px";
button.style.padding = "10px 20px";
button.style.backgroundColor = "#ff5a00";
button.style.color = "#fff";
button.style.border = "none";
button.style.cursor = "pointer";
button.style.fontSize = "16px";
button.style.borderRadius = "5px";
button.onclick = tuPokazujemyModal;
return button;
}
function tuPokazujemyModal() {
let modal = document.createElement("div");
modal.style.position = "fixed";
modal.style.top = "50%";
modal.style.left = "50%";
modal.style.transform = "translate(-50%, -50%)";
modal.style.backgroundColor = "#fff";
modal.style.padding = "35px";
modal.style.zIndex = "10000";
modal.style.boxShadow = "0px 0px 10px rgba(0, 0, 0, 0.5)";
modal.style.maxHeight = "80%";
modal.style.maxWidth = "80%";
modal.style.overflowY = "auto";
modal.style.display = "grid";
modal.style.gridTemplateColumns = "repeat(auto-fill, minmax(150px, 1fr))";
modal.style.gridGap = "10px";
modal.style.paddingBottom = "60px";
let tuZamykamy = document.createElement("button");
tuZamykamy.innerText = "x";
tuZamykamy.style.position = "absolute";
tuZamykamy.style.top = "5px";
tuZamykamy.style.right = "5px";
tuZamykamy.style.backgroundColor = "#ff5a00";
tuZamykamy.style.color = "#fff";
tuZamykamy.style.border = "none";
tuZamykamy.style.cursor = "pointer";
tuZamykamy.style.fontSize = "16px";
tuZamykamy.style.borderRadius = "50%";
tuZamykamy.style.width = "30px";
tuZamykamy.style.height = "30px";
tuZamykamy.style.zIndex = "9999";
tuZamykamy.onclick = function() { document.body.removeChild(modal); };
modal.appendChild(tuZamykamy);
let images = tuObrazki();
images.forEach(src => {
let imgWrapper = document.createElement("div");
imgWrapper.style.position = "relative";
imgWrapper.style.overflow = "hidden";
imgWrapper.style.border = "1px solid #ddd";
imgWrapper.style.borderRadius = "5px";
imgWrapper.style.padding = "5px";
imgWrapper.style.backgroundColor = "#f9f9f9";
let img = document.createElement("img");
img.src = src;
img.style.width = "150px";
img.style.height = "230px";
img.style.objectFit = "cover";
imgWrapper.appendChild(img);
let tuPobieramy = document.createElement("button");
tuPobieramy.innerText = "Pobierz";
tuPobieramy.style.position = "absolute";
tuPobieramy.style.bottom = "10px";
tuPobieramy.style.left = "50%";
tuPobieramy.style.transform = "translateX(-50%)";
tuPobieramy.style.padding = "5px 10px";
tuPobieramy.style.backgroundColor = "#ff5a00";
tuPobieramy.style.color = "#fff";
tuPobieramy.style.border = "none";
tuPobieramy.style.cursor = "pointer";
tuPobieramy.style.borderRadius = "3px";
tuPobieramy.onclick = function() { GM_download(src, tuMagia(src)); };
imgWrapper.appendChild(tuPobieramy);
modal.appendChild(imgWrapper);
});
let przyciskContainer = document.createElement("div");
przyciskContainer.style.width = "100%";
przyciskContainer.style.display = "flex";
przyciskContainer.style.justifyContent = "center";
przyciskContainer.style.position = "absolute";
przyciskContainer.style.bottom = "10px";
let tuWiecejPobieramy = document.createElement("button");
tuWiecejPobieramy.innerText = "Pobierz wszystkie";
tuWiecejPobieramy.style.padding = "10px 20px";
tuWiecejPobieramy.style.backgroundColor = "rgb(255, 90, 0)";
tuWiecejPobieramy.style.color = "rgb(255, 255, 255)";
tuWiecejPobieramy.style.border = "none";
tuWiecejPobieramy.style.cursor = "pointer";
tuWiecejPobieramy.style.fontSize = "16px";
tuWiecejPobieramy.style.borderRadius = "5px";
tuWiecejPobieramy.onclick = function() {
images.forEach(src => GM_download(src, tuMagia(src)));
};
przyciskContainer.appendChild(tuWiecejPobieramy);
modal.appendChild(przyciskContainer);
document.body.appendChild(modal);
}
function tuObrazki() {
let images = [];
let galleryDiv = document.querySelector('div[data-box-name="showoffer.gallery"]');
if (galleryDiv) {
let buttons = galleryDiv.querySelectorAll('button:not([aria-label])');
buttons.forEach(button => {
let img = button.querySelector('img');
if (img) {
let src = img.src.replace('/s128/', '/original/') + '.jpg';
images.push(src);
}
});
}
return images;
}
function tuMagia(src) {
return src.split('/').pop();
}
function tuPatrzymy() {
let observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.addedNodes.length > 0) {
let galleryDiv = document.querySelector('div[data-box-name="showoffer.gallery"]');
if (galleryDiv && !galleryDiv.querySelector("button.download-button")) {
let tuPobieramy = tuTworzymyButton();
tuPobieramy.classList.add("download-button");
galleryDiv.appendChild(tuPobieramy);
}
}
});
});
let config = { childList: true, subtree: true };
observer.observe(document.body, config);
}
tuPatrzymy();
})();
// --- Allegro Kampanie ---
(function() {
'use strict';
if (window.vSprintPlusSettings && window.vSprintPlusSettings.kampanie === false) return;
// Uruchamiaj tylko na stronie kampanii
if (!window.location.href.match(/^https:\/\/salescenter\.allegro\.com\/campaigns/)) {
return; // Przerywamy wykonywanie tego bloku
}
let listenForNextEligibleRequest = false;
let isCheckboxChecked = false; // Domyślnie checkbox jest odznaczony
let collectedRows = []; // Tablica do zbierania danych ze wszystkich stron
let pagesCollected = 0; // Licznik zebranych stron
let baseUrl = ''; // Bazowy URL do kolejnych żądań
let isAutoFetching = false; // Flaga czy trwa automatyczne pobieranie
let fetchOptions = null; // Opcje żądania fetch (headers, credentials, etc.)
let useXHR = false; // Flaga czy używać XMLHttpRequest czy fetch
// Zapisz oryginalny fetch przed jego nadpisaniem
const originalFetch = window.fetch;
const originalXHROpen = XMLHttpRequest.prototype.open;
const originalXHRSend = XMLHttpRequest.prototype.send;
// Funkcja dodająca przycisk i checkbox w prawym górnym rogu
function addDownloadPanel() {
const panel = document.createElement('div');
panel.style.position = 'fixed';
panel.style.top = '10px';
panel.style.right = '10px';
panel.style.zIndex = '9999';
panel.style.background = '#23272e';
panel.style.padding = '16px 20px';
panel.style.borderRadius = '10px';
panel.style.boxShadow = '0 4px 24px rgba(0,0,0,0.18)';
panel.style.display = 'flex';
panel.style.alignItems = 'center';
panel.style.gap = '16px';
panel.style.fontFamily = 'inherit';
// Przycisk
const button = document.createElement('button');
button.innerText = 'Pobierz raport';
button.style.background = '#23272e';
button.style.color = '#fff';
button.style.border = '1.5px solid #ff5a00';
button.style.padding = '10px 20px'; // rozmiar jak w oceny.js
button.style.fontSize = '16px';
button.style.borderRadius = '8px';
button.style.cursor = 'pointer';
button.style.fontWeight = 'bold';
button.style.boxShadow = '0 2px 8px rgba(0,0,0,0.10)';
button.style.transition = 'background 0.2s, color 0.2s';
button.onmouseenter = () => {
button.style.background = '#ff5a00';
button.style.color = '#fff';
};
button.onmouseleave = () => {
button.style.background = '#23272e';
button.style.color = '#fff';
};
button.style.top = '10px'; // pozycja jak w oceny.js
button.style.right = '20px';
// Checkbox
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.checked = isCheckboxChecked;
checkbox.style.width = '20px';
checkbox.style.height = '20px';
checkbox.style.cursor = 'pointer';
checkbox.style.accentColor = '#ff5a00';
checkbox.style.margin = '0 0 0 8px';
checkbox.title = 'Aktywuj/dezaktywuj zbieranie danych';
// Etykieta do checkboxa
const checkboxLabel = document.createElement('label');
checkboxLabel.innerText = 'Aktywne';
checkboxLabel.style.color = '#fff';
checkboxLabel.style.fontSize = '15px';
checkboxLabel.style.marginLeft = '4px';
checkboxLabel.style.cursor = 'pointer';
checkboxLabel.htmlFor = 'allegro-days-checkbox';
checkbox.id = 'allegro-days-checkbox';
// Instrukcja
const info = document.createElement('div');
info.style.position = 'absolute';
info.style.top = '54px'; // pod przyciskiem
info.style.left = '0';
info.style.width = '220px';
info.style.height = '220px';
info.style.display = 'none';
info.style.background = '#23272e';
info.style.border = '1.5px solid #ff5a00';
info.style.fontSize = '15px';
info.style.padding = '24px';
info.style.fontWeight = 'bold';
info.style.textAlign = 'center';
info.style.display = 'none';
info.style.alignItems = 'center';
info.style.justifyContent = 'center';
info.style.lineHeight = '1.5';
info.style.pointerEvents = 'none';
info.style.userSelect = 'none';
info.style.whiteSpace = 'pre-line';
info.style.transition = 'opacity 0.2s';
info.style.opacity = '1';
info.style.color = '#fff'; // Biały tekst na ciemnym tle
info.innerText = "aby pobrać raport,\nwybierz kampanię AlleObniżka,\nzjedź na sam dół i kliknij,\naby wyświetlało 200 ofert\nna jednej stronie (po lewej stronie).";
panel.style.position = 'fixed'; // upewnij się, że panel jest fixed
panel.appendChild(info);
// Obsługa checkboxa
checkbox.addEventListener('change', function() {
isCheckboxChecked = checkbox.checked;
console.log('Checkbox jest teraz:', isCheckboxChecked ? 'zaznaczony' : 'odznaczony');
});
// Obsługa przycisku
button.onclick = function() {
info.style.display = 'flex';
info.style.opacity = '1';
setTimeout(() => {
info.style.opacity = '0';
setTimeout(() => { info.style.display = 'none'; }, 300);
}, 15000);
};
// Panel: przycisk, checkbox, label
panel.appendChild(button);
panel.appendChild(checkbox);
panel.appendChild(checkboxLabel);
panel.appendChild(info);
document.body.appendChild(panel);
}
// Czekamy na załadowanie strony
window.addEventListener('load', function() {
addDownloadPanel();
});
// Nasłuchuj kliknięcia w przycisk value=200
document.addEventListener('click', function(event) {
const button = event.target.closest('button[value="200"]');
if (button) {
if (isCheckboxChecked) {
console.log('Checkbox jest zaznaczony – przygotowuję się do przechwycenia żądania...');
listenForNextEligibleRequest = true;
collectedRows = []; // Resetuj zebrane dane przy starcie nowego pobierania
pagesCollected = 0;
baseUrl = ''; // Resetuj bazowy URL
isAutoFetching = false; // Resetuj flagę automatycznego pobierania
fetchOptions = null; // Resetuj opcje fetch
useXHR = false; // Resetuj flagę XHR
console.log('[ZBIERACZ] Rozpoczynam automatyczne zbieranie danych z wielu stron...');
} else {
console.log('Checkbox NIE jest zaznaczony – skrypt nie zostanie uruchomiony.');
listenForNextEligibleRequest = false;
isAutoFetching = false;
}
}
});
// Funkcja pomocnicza do przetwarzania danych
function processItems(items) {
return items.map(item => {
// Oblicz procentową obniżkę
const price = parseFloat(item.buyNowPrice.amount.replace(',', '.'));
const discountPrice = parseFloat(item.cofinanceInfo.requiredMerchantPrice.amount.replace(',', '.'));
const discountPercentage = ((price - discountPrice) / price) * 100;
// Zapisujemy procent jako liczbę (np. 13.43) zamiast tekstu
const discountPercentageRounded = parseFloat(discountPercentage.toFixed(2)); // Zaokrąglamy i zostawiamy jako liczbę
// Oblicz dodatek Allegro
const merchantDiscount = parseFloat(item.cofinanceInfo.merchantPriceDiscount.points);
const discountAddition = (merchantDiscount / 100) * discountPrice;
// Sprawdź czy oferta się kwalifikuje
const kwalifikuje = item.notMetConditions && item.notMetConditions.length > 0
? item.notMetConditions.join('; ')
: 'tak';
// Zwróć dane w odpowiedniej strukturze
return {
id: item.offerId,
sku: item.externalId,
ean: item.ean,
nazwa: item.offerTitle,
cena: price, // Zapisujemy liczbę jako liczbę
wymagana_obnizka: discountPrice, // Zapisujemy liczbę jako liczbę
procent_obnizki: discountPercentageRounded, // Zapisujemy liczbę jako liczbę
limit: item.cofinanceInfo.purchaseLimit,
obnizka_allegro: discountAddition, // Zapisujemy liczbę jako liczbę
czy_sie_kwalifikuje: kwalifikuje,
};
});
}
// Funkcja do wyświetlania modala
function showModal(message) {
// Usuń istniejący modal jeśli jest
const existingModal = document.getElementById('allegro-download-modal');
if (existingModal) {
existingModal.remove();
}
// Tworzenie overlay
const overlay = document.createElement('div');
overlay.id = 'allegro-download-modal';
overlay.style.position = 'fixed';
overlay.style.top = '0';
overlay.style.left = '0';
overlay.style.width = '100%';
overlay.style.height = '100%';
overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
overlay.style.zIndex = '10000';
overlay.style.display = 'flex';
overlay.style.alignItems = 'center';
overlay.style.justifyContent = 'center';
// Tworzenie modala
const modal = document.createElement('div');
modal.style.background = '#23272e';
modal.style.padding = '30px 40px';
modal.style.borderRadius = '12px';
modal.style.boxShadow = '0 8px 32px rgba(0,0,0,0.3)';
modal.style.border = '2px solid #ff5a00';
modal.style.maxWidth = '400px';
modal.style.textAlign = 'center';
modal.style.color = '#fff';
modal.style.fontSize = '16px';
modal.style.fontFamily = 'inherit';
// Tekst w modalu
const text = document.createElement('div');
text.innerText = message;
text.style.marginBottom = '20px';
text.style.lineHeight = '1.5';
// Przycisk zamknięcia
const closeBtn = document.createElement('button');
closeBtn.innerText = 'Zamknij';
closeBtn.style.background = '#ff5a00';
closeBtn.style.color = '#fff';
closeBtn.style.border = 'none';
closeBtn.style.padding = '12px 30px';
closeBtn.style.fontSize = '16px';
closeBtn.style.borderRadius = '8px';
closeBtn.style.cursor = 'pointer';
closeBtn.style.fontWeight = 'bold';
closeBtn.style.transition = 'background 0.2s';
closeBtn.onmouseenter = () => {
closeBtn.style.background = '#e04a00';
};
closeBtn.onmouseleave = () => {
closeBtn.style.background = '#ff5a00';
};
closeBtn.onclick = () => {
overlay.remove();
};
modal.appendChild(text);
modal.appendChild(closeBtn);
overlay.appendChild(modal);
// Kliknięcie poza modalem zamyka go
overlay.onclick = (e) => {
if (e.target === overlay) {
overlay.remove();
}
};
document.body.appendChild(overlay);
}
// Funkcja do wyświetlania okienka pobierania
let downloadingModal = null;
function showDownloadingModal(pagesCount, itemsCount) {
// Usuń istniejące okienko jeśli jest
if (downloadingModal) {
downloadingModal.remove();
}
// Tworzenie overlay
const overlay = document.createElement('div');
overlay.id = 'allegro-downloading-modal';
downloadingModal = overlay;
overlay.style.position = 'fixed';
overlay.style.top = '0';
overlay.style.left = '0';
overlay.style.width = '100%';
overlay.style.height = '100%';
overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.6)';
overlay.style.zIndex = '10000';
overlay.style.display = 'flex';
overlay.style.alignItems = 'center';
overlay.style.justifyContent = 'center';
// Tworzenie okienka
const box = document.createElement('div');
box.style.background = '#23272e';
box.style.padding = '30px 40px';
box.style.borderRadius = '12px';
box.style.boxShadow = '0 8px 32px rgba(0,0,0,0.3)';
box.style.border = '2px solid #ff5a00';
box.style.minWidth = '300px';
box.style.textAlign = 'center';
box.style.color = '#fff';
box.style.fontSize = '16px';
box.style.fontFamily = 'inherit';
// Ikona/animacja ładowania
const spinner = document.createElement('div');
spinner.style.width = '40px';
spinner.style.height = '40px';
spinner.style.border = '4px solid rgba(255, 90, 0, 0.3)';
spinner.style.borderTop = '4px solid #ff5a00';
spinner.style.borderRadius = '50%';
spinner.style.animation = 'spin 1s linear infinite';
spinner.style.margin = '0 auto 20px';
// Dodaj animację spin
if (!document.getElementById('allegro-spinner-style')) {
const style = document.createElement('style');
style.id = 'allegro-spinner-style';
style.textContent = `
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
`;
document.head.appendChild(style);
}
// Tekst
const text = document.createElement('div');
text.id = 'allegro-downloading-text';
text.innerText = `Pobieranie danych...\nStrona: ${pagesCount} | Ofert: ${itemsCount}`;
text.style.marginBottom = '10px';
text.style.lineHeight = '1.8';
text.style.whiteSpace = 'pre-line';
box.appendChild(spinner);
box.appendChild(text);
overlay.appendChild(box);
document.body.appendChild(overlay);
}
// Funkcja do aktualizacji okienka pobierania
function updateDownloadingModal(pagesCount, itemsCount) {
const textElement = document.getElementById('allegro-downloading-text');
if (textElement) {
textElement.innerText = `Pobieranie danych...\nStrona: ${pagesCount} | Ofert: ${itemsCount}`;
}
}
// Funkcja do zamykania okienka pobierania
function hideDownloadingModal() {
if (downloadingModal) {
downloadingModal.remove();
downloadingModal = null;
}
}
// Funkcja do zapisywania zebranych danych do Excela
function saveCollectedData() {
if (collectedRows.length === 0) {
console.warn('[ZBIERACZ] Brak danych do zapisania.');
hideDownloadingModal();
return;
}
console.log(`[ZBIERACZ] Zapisuję dane z ${pagesCollected} stron (łącznie ${collectedRows.length} ofert)...`);
// Zapis do pliku Excel
const ws = XLSX.utils.json_to_sheet(collectedRows);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "Dane");
// Ustawienia formatu komórek w Excelu
const cellFormat = {
cellDates: true,
cellText: false
};
// Formatuj kolumnę procent_obnizki jako procentową
const percentStyle = {
font: { bold: false },
alignment: { horizontal: 'right' },
numberFormat: '0.00%'
};
// Aplikuj formatowanie dla procentów
ws['!cols'] = ws['!cols'] || [];
ws['!cols'][2] = percentStyle; // Kolumna 2 (procent_obnizki) jako procentowa
// Zapisujemy plik z nową nazwą
XLSX.writeFile(wb, "AllegroDays - zestawienie.xlsx", cellFormat);
// Zamknij okienko pobierania
hideDownloadingModal();
// Wyświetl informację o liczbie stron w modalu
showModal(`Pobrano raport z ${pagesCollected} stron (łącznie ${collectedRows.length} ofert).`);
// Po pobraniu raportu odznacz checkbox
const cb = document.getElementById('allegro-days-checkbox');
if (cb) cb.checked = false, isCheckboxChecked = false;
// Resetuj zebrane dane
collectedRows = [];
pagesCollected = 0;
listenForNextEligibleRequest = false;
isAutoFetching = false;
baseUrl = '';
fetchOptions = null;
useXHR = false;
}
// Funkcja do automatycznego pobierania kolejnych stron przez fetch
function fetchNextPage(offset) {
if (!baseUrl || isAutoFetching === false || !fetchOptions) {
return;
}
// Utwórz URL z nowym offsetem
let nextUrl = baseUrl;
if (nextUrl.includes('offset=')) {
// Zamień istniejący offset
nextUrl = nextUrl.replace(/offset=\d+/, `offset=${offset}`);
} else {
// Dodaj offset do URL
const separator = nextUrl.includes('?') ? '&' : '?';
nextUrl = `${nextUrl}${separator}offset=${offset}`;
}
console.log(`[ZBIERACZ] Automatyczne pobieranie strony z offset=${offset}: ${nextUrl}`);
// Skopiuj opcje
const options = { ...fetchOptions };
if (!options.credentials || options.credentials === 'same-origin') {
options.credentials = 'include';
}
if (options.headers && !(options.headers instanceof Headers)) {
options.headers = { ...options.headers };
}
// Wykonaj żądanie z zapisanymi opcjami używając originalFetch, aby uniknąć przechwycenia
originalFetch(nextUrl, options)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
console.log(`[ZBIERACZ] Automatyczna odpowiedź (offset=${offset}):`, data);
// Sprawdź, czy odpowiedź zawiera pole 'items'
if (data.items && Array.isArray(data.items)) {
// Jeśli brak ofert (pusta tablica), zapisz zebrane dane
if (data.items.length === 0) {
console.log('[ZBIERACZ] Otrzymano pustą odpowiedź - kończę automatyczne pobieranie i zapisuję dane.');
isAutoFetching = false;
saveCollectedData();
return;
}
// Przetwarzanie danych na odpowiedni format dla Excela
const rows = processItems(data.items);
// Dodaj dane do zebranych
collectedRows = collectedRows.concat(rows);
pagesCollected++;
// Aktualizuj okienko pobierania
updateDownloadingModal(pagesCollected, collectedRows.length);
console.log(`[ZBIERACZ] Automatycznie zebrano stronę ${pagesCollected} (${data.items.length} ofert). Łącznie: ${collectedRows.length} ofert.`);
// Pobierz następną stronę z offsetem zwiększonym o limit (200)
setTimeout(() => {
fetchNextPage(offset + 200);
}, 500); // Małe opóźnienie aby nie przeciążać serwera
} else {
// Jeśli nie ma items, oznacza to koniec - zapisz zebrane dane
console.log('[ZBIERACZ] Brak pola "items" w odpowiedzi - kończę automatyczne pobieranie i zapisuję dane.');
isAutoFetching = false;
if (collectedRows.length > 0) {
saveCollectedData();
} else {
console.warn('[ZBIERACZ] Brak danych do zapisania.');
listenForNextEligibleRequest = false;
}
}
})
.catch(error => {
console.error('[ZBIERACZ] Błąd podczas automatycznego pobierania:', error);
isAutoFetching = false;
// Zapisz to co udało się zebrać
if (collectedRows.length > 0) {
saveCollectedData();
}
});
}
// Funkcja do automatycznego pobierania kolejnych stron przez XMLHttpRequest
function fetchNextPageXHR(offset) {
if (!baseUrl || isAutoFetching === false || !fetchOptions) {
return;
}
// Utwórz URL z nowym offsetem
let nextUrl = baseUrl;
if (nextUrl.includes('offset=')) {
// Zamień istniejący offset
nextUrl = nextUrl.replace(/offset=\d+/, `offset=${offset}`);
} else {
// Dodaj offset do URL
const separator = nextUrl.includes('?') ? '&' : '?';
nextUrl = `${nextUrl}${separator}offset=${offset}`;
}
console.log(`[ZBIERACZ][XHR] Automatyczne pobieranie strony z offset=${offset}: ${nextUrl}`);
// Utwórz nowe żądanie XMLHttpRequest
const xhr = new XMLHttpRequest();
// Otwórz żądanie z metodą GET
originalXHROpen.call(xhr, 'GET', nextUrl, true);
// Ustaw nagłówki z zapisanych opcji
if (fetchOptions.headers) {
Object.keys(fetchOptions.headers).forEach(key => {
xhr.setRequestHeader(key, fetchOptions.headers[key]);
});
}
xhr.withCredentials = true;
// Obsługa odpowiedzi
xhr.onload = function() {
try {
if (xhr.status >= 200 && xhr.status < 300) {
const data = JSON.parse(xhr.responseText);
console.log(`[ZBIERACZ][XHR] Automatyczna odpowiedź (offset=${offset}):`, data);
// Sprawdź, czy odpowiedź zawiera pole 'items'
if (data.items && Array.isArray(data.items)) {
// Jeśli brak ofert (pusta tablica), zapisz zebrane dane
if (data.items.length === 0) {
console.log('[ZBIERACZ][XHR] Otrzymano pustą odpowiedź - kończę automatyczne pobieranie i zapisuję dane.');
isAutoFetching = false;
saveCollectedData();
return;
}
// Przetwarzanie danych na odpowiedni format dla Excela
const rows = processItems(data.items);
// Dodaj dane do zebranych
collectedRows = collectedRows.concat(rows);
pagesCollected++;
// Aktualizuj okienko pobierania
updateDownloadingModal(pagesCollected, collectedRows.length);
console.log(`[ZBIERACZ][XHR] Automatycznie zebrano stronę ${pagesCollected} (${data.items.length} ofert). Łącznie: ${collectedRows.length} ofert.`);
// Pobierz następną stronę z offsetem zwiększonym o limit (200)
setTimeout(() => {
fetchNextPageXHR(offset + 200);
}, 500); // Małe opóźnienie aby nie przeciążać serwera
} else {
// Jeśli nie ma items, oznacza to koniec - zapisz zebrane dane
console.log('[ZBIERACZ][XHR] Brak pola "items" w odpowiedzi - kończę automatyczne pobieranie i zapisuję dane.');
isAutoFetching = false;
if (collectedRows.length > 0) {
saveCollectedData();
} else {
console.warn('[ZBIERACZ][XHR] Brak danych do zapisania.');
listenForNextEligibleRequest = false;
}
}
} else {
throw new Error(`HTTP error! status: ${xhr.status}`);
}
} catch (error) {
console.error('[ZBIERACZ][XHR] Błąd podczas automatycznego pobierania:', error);
isAutoFetching = false;
// Zapisz to co udało się zebrać
if (collectedRows.length > 0) {
saveCollectedData();
}
}
};
xhr.onerror = function() {
console.error('[ZBIERACZ][XHR] Błąd sieci podczas automatycznego pobierania');
isAutoFetching = false;
if (collectedRows.length > 0) {
saveCollectedData();
}
};
// Wysyłaj żądanie
originalXHRSend.call(xhr);
}
// Przechwytywanie fetch
window.fetch = function(...args) {
const requestUrl = args[0];
// Sprawdź czy to żądanie eligible z limit=200 (dopasowuje różne kampanie)
const isEligibleRequest = listenForNextEligibleRequest &&
typeof requestUrl === 'string' &&
requestUrl.includes('eligible') &&
requestUrl.includes('limit=200');
if (isEligibleRequest && !isAutoFetching) {
// To jest pierwsze żądanie - zapisz bazowy URL i opcje żądania
baseUrl = requestUrl;
isAutoFetching = true;
useXHR = false; // Używamy fetch, nie XHR
// Zapisz opcje żądania (drugi argument fetch, jeśli istnieje)
if (args[1]) {
// Konwertuj headers na zwykły obiekt jeśli jest Headers object
let headersObj = {};
if (args[1].headers) {
if (args[1].headers instanceof Headers) {
args[1].headers.forEach((value, key) => {
headersObj[key] = value;
});
} else if (typeof args[1].headers === 'object' && !Array.isArray(args[1].headers)) {
// Jeśli to zwykły obiekt, skopiuj go
headersObj = { ...args[1].headers };
}
}
// Skopiuj opcje - szczególnie ważne są headers, credentials, etc.
fetchOptions = {
method: args[1].method || 'GET',
headers: headersObj,
credentials: args[1].credentials !== undefined ? args[1].credentials : 'include',
cache: args[1].cache || 'default',
redirect: args[1].redirect || 'follow',
referrer: args[1].referrer || 'client',
referrerPolicy: args[1].referrerPolicy || '',
mode: args[1].mode || 'cors'
// Nie kopiujemy signal i body - nie są potrzebne dla GET
};
console.log('[ZBIERACZ] Zapisano opcje żądania:', fetchOptions);
} else {
// Domyślne opcje jeśli nie podano
fetchOptions = {
method: 'GET',
credentials: 'include',
headers: {}
};
}
// Wyciągnij offset z URL do logowania
const urlMatch = requestUrl.match(/offset=(\d+)/);
const currentOffset = urlMatch ? parseInt(urlMatch[1]) : 0;
console.log(`[ZBIERACZ] Wykryto pierwsze żądanie (offset=${currentOffset}):`, requestUrl);
console.log(`[ZBIERACZ] Rozpoczynam automatyczne pobieranie z wielu stron...`);
return originalFetch(...args).then(response => {
const clonedResponse = response.clone();
clonedResponse.json().then(data => {
console.log(`[ZBIERACZ] Odpowiedź pierwszej strony (offset=${currentOffset}):`, data);
// Sprawdź, czy odpowiedź zawiera pole 'items'
if (data.items && Array.isArray(data.items)) {
// Jeśli brak ofert (pusta tablica), zapisz i zakończ
if (data.items.length === 0) {
console.log('[ZBIERACZ] Pierwsza strona jest pusta - kończę zbieranie i zapisuję dane.');
isAutoFetching = false;
saveCollectedData();
return;
}
// Przetwarzanie danych na odpowiedni format dla Excela
const rows = processItems(data.items);
// Dodaj dane do zebranych
collectedRows = collectedRows.concat(rows);
pagesCollected++;
// Pokaż okienko pobierania
showDownloadingModal(pagesCollected, collectedRows.length);
console.log(`[ZBIERACZ] Zebrano stronę ${pagesCollected} (${data.items.length} ofert). Łącznie: ${collectedRows.length} ofert.`);
console.log(`[ZBIERACZ] Rozpoczynam automatyczne pobieranie kolejnych stron...`);
// Rozpocznij automatyczne pobieranie kolejnych stron
setTimeout(() => {
fetchNextPage(currentOffset + 200);
}, 500);
} else {
// Jeśli nie ma items, oznacza to koniec - zapisz zebrane dane
console.log('[ZBIERACZ] Brak pola "items" w odpowiedzi pierwszej strony - kończę zbieranie.');
isAutoFetching = false;
if (collectedRows.length > 0) {
saveCollectedData();
} else {
console.warn('[ZBIERACZ] Brak danych do zapisania.');
listenForNextEligibleRequest = false;
}
}
});
return response;
});
}
return originalFetch(...args);
};
// Przechwytywanie XMLHttpRequest - przechwytujemy setRequestHeader aby zapisać nagłówki
const originalSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
XMLHttpRequest.prototype.setRequestHeader = function(header, value) {
// Jeśli to nasze żądanie, zapisz nagłówek
if (this._isEligibleRequest && this._headers) {
this._headers[header] = value;
}
return originalSetRequestHeader.call(this, header, value);
};
XMLHttpRequest.prototype.open = function(method, url, ...rest) {
// Sprawdź czy to żądanie eligible z limit=200 (dopasowuje różne kampanie)
this._isEligibleRequest = listenForNextEligibleRequest &&
typeof url === 'string' &&
url.includes('eligible') &&
url.includes('limit=200') &&
!isAutoFetching; // Tylko pierwsze żądanie
if (this._isEligibleRequest) {
this._requestUrl = url; // Zapisz URL dla późniejszego użycia
this._headers = {}; // Inicjalizuj obiekt do przechowywania nagłówków
this._method = method; // Zapisz metodę
}
return originalXHROpen.call(this, method, url, ...rest);
};
XMLHttpRequest.prototype.send = function(...args) {
if (this._isEligibleRequest) {
const xhr = this;
const requestUrl = this._requestUrl || '';
// To jest pierwsze żądanie - zapisz bazowy URL, nagłówki i rozpocznij automatyczne pobieranie
baseUrl = requestUrl;
isAutoFetching = true;
useXHR = true; // Oznacz, że używamy XHR
// Zapisz nagłówki i inne opcje jako fetchOptions dla spójności
fetchOptions = {
method: this._method || 'GET',
headers: this._headers || {},
credentials: 'include', // XHR domyślnie wysyła cookies
mode: 'cors'
};
console.log('[ZBIERACZ][XHR] Zapisano opcje żądania:', fetchOptions);
// Wyciągnij offset z URL do logowania
const urlMatch = requestUrl.match(/offset=(\d+)/);
const currentOffset = urlMatch ? parseInt(urlMatch[1]) : 0;
console.log(`[ZBIERACZ][XHR] Wykryto pierwsze żądanie (offset=${currentOffset}):`, requestUrl);
console.log(`[ZBIERACZ][XHR] Rozpoczynam automatyczne pobieranie z wielu stron...`);
const onLoad = function() {
try {
const response = JSON.parse(xhr.responseText);
console.log(`[ZBIERACZ][XHR] Odpowiedź pierwszej strony (offset=${currentOffset}):`, response);
// Sprawdź, czy odpowiedź zawiera pole 'items'
if (response.items && Array.isArray(response.items)) {
// Jeśli brak ofert (pusta tablica), zapisz i zakończ
if (response.items.length === 0) {
console.log('[ZBIERACZ][XHR] Pierwsza strona jest pusta - kończę zbieranie i zapisuję dane.');
isAutoFetching = false;
saveCollectedData();
return;
}
// Przetwarzanie danych na odpowiedni format dla Excela
const rows = processItems(response.items);
// Dodaj dane do zebranych
collectedRows = collectedRows.concat(rows);
pagesCollected++;
// Pokaż okienko pobierania
showDownloadingModal(pagesCollected, collectedRows.length);
console.log(`[ZBIERACZ][XHR] Zebrano stronę ${pagesCollected} (${response.items.length} ofert). Łącznie: ${collectedRows.length} ofert.`);
console.log(`[ZBIERACZ][XHR] Rozpoczynam automatyczne pobieranie kolejnych stron...`);
// Rozpocznij automatyczne pobieranie kolejnych stron używając XHR
setTimeout(() => {
fetchNextPageXHR(currentOffset + 200);
}, 500);
} else {
// Jeśli nie ma items, oznacza to koniec - zapisz zebrane dane
console.log('[ZBIERACZ][XHR] Brak pola "items" w odpowiedzi pierwszej strony - kończę zbieranie.');
isAutoFetching = false;
if (collectedRows.length > 0) {
saveCollectedData();
} else {
console.warn('[ZBIERACZ][XHR] Brak danych do zapisania.');
listenForNextEligibleRequest = false;
}
}
} catch (e) {
console.warn('[ZBIERACZ][XHR] Nie udało się sparsować odpowiedzi:', e);
isAutoFetching = false;
}
};
this.addEventListener('load', onLoad);
}
return originalXHRSend.apply(this, args);
};
}
)();
// --- Allegro Kampanie Filtr ---
(function() {
'use strict';
if (window.vSprintPlusSettings && window.vSprintPlusSettings.kampFiltr === false) return;
// Uruchamiaj tylko na stronie kampanii
if (!window.location.href.match(/^https:\/\/salescenter\.allegro\.com\/campaigns/)) {
return; // Przerywamy wykonywanie tego bloku
}
'use strict';
// Funkcja do tworzenia i dodania wyszukiwarki
function addSearchBox() {
const searchContainer = document.querySelector('[data-testid="reco-offers"]');
// Sprawdź czy już dodano wyszukiwarkę
if (document.getElementById('allegro-search-wrapper')) {
return; // Już dodano, nie dodawaj ponownie
}
if (searchContainer) {
// Tworzymy kontener dla wyszukiwarki i przycisku
const wrapper = document.createElement('div');
wrapper.id = 'allegro-search-wrapper';
wrapper.style.marginTop = '20px';
wrapper.style.marginBottom = '20px';
// Tworzymy pole wyszukiwania
const searchBox = document.createElement('input');
searchBox.setAttribute('id', 'offerSearchBox');
searchBox.setAttribute('placeholder', 'Wpisz numery ofert (np. 12345, 67890)');
searchBox.style.width = '100%';
searchBox.style.padding = '12px 16px';
searchBox.style.marginBottom = '12px';
searchBox.style.fontSize = '16px';
searchBox.style.background = '#fff'; // Standardowe białe tło
searchBox.style.color = '#000'; // Czarny tekst
searchBox.style.border = '1.5px solid #ff5a00'; // Pomarańczowa ramka
searchBox.style.borderRadius = '8px';
searchBox.style.fontFamily = 'inherit';
searchBox.style.boxSizing = 'border-box';
searchBox.style.outline = 'none';
searchBox.style.transition = 'border-color 0.2s';
// Dodaj style dla placeholder (pogrubiony)
if (!document.getElementById('allegro-search-placeholder-style')) {
const style = document.createElement('style');
style.id = 'allegro-search-placeholder-style';
style.textContent = `
#offerSearchBox::placeholder {
color: rgba(0, 0, 0, 0.6);
font-weight: bold;
}
`;
document.head.appendChild(style);
}
searchBox.addEventListener('input', filterOffers);
searchBox.addEventListener('focus', () => {
searchBox.style.borderColor = '#ff7a30';
});
searchBox.addEventListener('blur', () => {
searchBox.style.borderColor = '#ff5a00';
});
// Tworzymy przycisk masowego zaznaczania w stylu przycisku "Pobierz raport"
const massSelectButton = document.createElement('button');
massSelectButton.textContent = 'Zaznacz wszystkie widoczne';
massSelectButton.style.background = '#fff'; // Białe tło jak standardowe przyciski
massSelectButton.style.color = '#000'; // Czarny tekst
massSelectButton.style.border = '1.5px solid #ff5a00'; // Pomarańczowa ramka
massSelectButton.style.padding = '10px 20px';
massSelectButton.style.fontSize = '16px';
massSelectButton.style.borderRadius = '8px';
massSelectButton.style.cursor = 'pointer';
massSelectButton.style.fontWeight = 'bold';
massSelectButton.style.boxShadow = '0 2px 8px rgba(0,0,0,0.10)';
massSelectButton.style.transition = 'background 0.2s, color 0.2s, border-color 0.2s';
massSelectButton.style.fontFamily = 'inherit';
massSelectButton.style.display = 'inline-block'; // Nie na całą szerokość
massSelectButton.style.textAlign = 'left'; // Wyrównanie do lewej
massSelectButton.addEventListener('click', toggleCheckboxes);
// Efekty hover
massSelectButton.onmouseenter = () => {
massSelectButton.style.background = '#ff5a00';
massSelectButton.style.color = '#fff';
massSelectButton.style.borderColor = '#ff5a00';
};
massSelectButton.onmouseleave = () => {
massSelectButton.style.background = '#fff';
massSelectButton.style.color = '#000';
massSelectButton.style.borderColor = '#ff5a00';
};
// Dodajemy elementy do kontenera
wrapper.appendChild(searchBox);
wrapper.appendChild(massSelectButton);
// Dodajemy wrapper przed kontenerem z ofertami
searchContainer.insertAdjacentElement('beforebegin', wrapper);
console.log('[KAMPANIE FILTR] Wyszukiwarka i przycisk dodane!');
} else {
console.log('[KAMPANIE FILTR] Nie znaleziono kontenera z ofertami, próbuję ponownie...');
}
}
// Funkcja do filtrowania ofert na podstawie numerów
function filterOffers() {
const input = document.getElementById('offerSearchBox').value.trim();
console.log('Wyszukiwanie dla:', input); // Logujemy wartość wpisaną w pole wyszukiwania
const numbers = input.split(/[\s,]+/).filter(Boolean); // Dzielimy po spacji lub przecinku i usuwamy puste elementy
if (numbers.length > 0) {
const offers = document.querySelectorAll('.mpof_ki');
console.log('Znalezione oferty:', offers.length); // Logujemy liczbę ofert
offers.forEach(offer => {
const offerId = offer.querySelector('[data-testid="campaigns-recolist-offerId"]');
if (offerId && numbers.includes(offerId.textContent.trim())) {
offer.style.display = ''; // Pokaż ofertę
} else {
offer.style.display = 'none'; // Ukryj ofertę
}
});
} else {
// Jeśli brak numerów, pokazujemy wszystkie oferty
const offers = document.querySelectorAll('.mpof_ki');
offers.forEach(offer => {
offer.style.display = ''; // Pokaż ofertę
});
}
}
// Funkcja do masowego zaznaczania checkboxów
function toggleCheckboxes() {
const offers = document.querySelectorAll('.mpof_ki');
offers.forEach(offer => {
// Sprawdzamy, czy oferta jest widoczna
if (offer.style.display !== 'none') {
const checkbox = offer.querySelector('input[type="checkbox"]');
if (checkbox) {
// Zmieniamy stan checkboxa
checkbox.checked = !checkbox.checked;
// Wymuszamy wywołanie zdarzenia change
const event = new Event('change', {
'bubbles': true,
'cancelable': true
});
checkbox.dispatchEvent(event);
console.log(`Checkbox dla oferty ${offer.querySelector('[data-testid="campaigns-recolist-offerId"]').textContent} zmieniony na: ${checkbox.checked}`);
}
}
});
}
// Funkcja inicjalizująca - próbuje dodać wyszukiwarkę natychmiast i ustawia obserwatora
function initSearchBox() {
// Spróbuj dodać natychmiast
addSearchBox();
// Jeśli nie udało się, ustaw obserwatora DOM
if (!document.getElementById('allegro-search-wrapper')) {
const observer = new MutationObserver((mutations, obs) => {
// Sprawdź czy kontener z ofertami się pojawił
const searchContainer = document.querySelector('[data-testid="reco-offers"]');
if (searchContainer && !document.getElementById('allegro-search-wrapper')) {
addSearchBox();
// Jeśli udało się dodać, można przestać obserwować (opcjonalnie)
// obs.disconnect();
}
});
// Rozpocznij obserwację całego body
observer.observe(document.body, {
childList: true,
subtree: true
});
console.log('[KAMPANIE FILTR] Ustawiono obserwatora DOM dla wyszukiwarki');
}
}
// Inicjalizuj po załadowaniu strony lub natychmiast jeśli DOM jest gotowy
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initSearchBox);
} else {
initSearchBox();
}
// Również spróbuj po pełnym załadowaniu strony
window.addEventListener('load', () => {
console.log('[KAMPANIE FILTR] Strona załadowana, sprawdzam wyszukiwarkę...');
addSearchBox();
});
})();
// --- vSprint+ PANEL USTAWIEŃ ---
(function() {
'use strict';
const vSprintFeatures = [
{ key: 'oceny', label: 'Raport z ocen' },
{ key: 'filtr', label: 'Wyszukiwarka SKU/ID' },
{ key: 'info', label: 'vSprint info' },
{ key: 'img', label: 'Image/Desc Downloader' },
{ key: 'kampanie', label: 'Asystent Kampanii' },
{ key: 'kampFiltr', label: 'Filtr ofert w kampaniach' },
{ key: 'kopiujId', label: 'Kopiuj ID' },
{ key: 'edytuj', label: 'Edytuj' }
];
function getVSprintSettings() {
let settings = {};
try { settings = JSON.parse(localStorage.getItem('vSprintPlusSettings') || '{}'); } catch (e) {}
vSprintFeatures.forEach(f => { if (typeof settings[f.key] === 'undefined') settings[f.key] = true; });
return settings;
}
function saveVSprintSettings(settings) {
localStorage.setItem('vSprintPlusSettings', JSON.stringify(settings));
}
function createVSprintButton() {
if (document.getElementById('vsprintplus-btn')) return;
const btn = document.createElement('button');
btn.id = 'vsprintplus-btn';
btn.textContent = 'vS+';
btn.style.position = 'fixed';
btn.style.right = '0px';
btn.style.bottom = '24px';
btn.style.borderRadius = '16px 0 0 16px';
btn.style.zIndex = '99999';
btn.style.background = 'rgb(255, 90, 0)';
btn.style.color = '#fff';
btn.style.border = 'none';
btn.style.padding = '10px 10px 10px 18px';
btn.style.fontSize = '16px';
btn.style.boxShadow = '0 2px 8px rgba(0,0,0,0.15)';
btn.style.cursor = 'pointer';
btn.style.transition = 'width 0.2s, min-width 0.2s, padding 0.2s, background 0.2s, color 0.2s';
btn.style.overflow = 'hidden';
btn.style.whiteSpace = 'nowrap';
btn.style.width = '54px';
btn.style.minWidth = '54px';
btn.style.textAlign = 'center';
btn.onmouseenter = function() {
btn.textContent = 'vSprint Allegro+';
btn.style.width = '148px';
btn.style.minWidth = '148px';
btn.style.padding = '10px 18px 10px 18px';
};
btn.onmouseleave = function() {
btn.textContent = 'vS+';
btn.style.width = '54px';
btn.style.minWidth = '54px';
btn.style.padding = '10px 10px 10px 18px';
};
btn.onclick = showVSprintPanel;
document.body.appendChild(btn);
}
function showVSprintPanel() {
if (document.getElementById('vsprintplus-modal')) return;
const settings = getVSprintSettings();
const modal = document.createElement('div');
modal.id = 'vsprintplus-modal';
modal.style.position = 'fixed';
modal.style.right = '24px';
modal.style.bottom = '70px';
modal.style.background = '#fff';
modal.style.border = '1.5px solid #ff5a00';
modal.style.borderRadius = '12px';
modal.style.boxShadow = '0 4px 24px rgba(0,0,0,0.18)';
modal.style.padding = '28px 32px 20px 32px';
modal.style.zIndex = '100000';
modal.style.minWidth = '300px';
modal.style.fontSize = '16px';
modal.style.display = 'flex';
modal.style.flexDirection = 'column';
modal.style.gap = '14px';
modal.style.fontFamily = 'inherit';
// Nagłówek
const title = document.createElement('div');
title.textContent = 'vSprint Allegro+';
title.style.fontWeight = 'bold';
title.style.fontSize = '20px';
title.style.marginBottom = '16px';
title.style.color = '#ff5a00';
title.style.letterSpacing = '1px';
title.style.textAlign = 'center';
modal.appendChild(title);
// Checkboxy
vSprintFeatures.forEach(f => {
const row = document.createElement('label');
row.style.display = 'flex';
row.style.fontFamily = '"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif';
row.style.alignItems = 'center';
row.style.gap = '10px';
row.style.color = '#23272e';
row.style.fontSize = '16px';
row.style.padding = '4px 0';
const cb = document.createElement('input');
cb.type = 'checkbox';
cb.checked = !!settings[f.key];
cb.style.width = '20px';
cb.style.height = '20px';
cb.style.accentColor = '#ff5a00';
cb.onchange = function() {
settings[f.key] = cb.checked;
saveVSprintSettings(settings);
};
row.appendChild(cb);
row.appendChild(document.createTextNode(f.label));
modal.appendChild(row);
});
// Zamknięcie
const closeBtn = document.createElement('button');
closeBtn.textContent = 'Zamknij';
closeBtn.style.marginTop = '22px';
closeBtn.style.alignSelf = 'center';
closeBtn.style.background = 'rgb(255, 90, 0)';
closeBtn.style.color = '#fff';
closeBtn.style.border = 'none';
closeBtn.style.borderRadius = '8px';
closeBtn.style.padding = '10px 28px';
closeBtn.style.fontSize = '16px';
closeBtn.style.cursor = 'pointer';
closeBtn.style.fontWeight = 'bold';
closeBtn.style.boxShadow = '0 2px 8px rgba(0,0,0,0.10)';
closeBtn.onclick = () => modal.remove();
modal.appendChild(closeBtn);
document.body.appendChild(modal);
}
createVSprintButton();
})();
// --- ALLEGRO OFERTA PRZYCISKI ---
(function() {
'use strict';
// Sprawdzamy czy funkcje są włączone
if (window.vSprintPlusSettings && window.vSprintPlusSettings.edytuj === false &&
window.vSprintPlusSettings && window.vSprintPlusSettings.kopiujId === false) return;
// Uruchamiaj tylko na stronach ofert (stary /oferta/ i nowy /produkt/)
if (!window.location.href.match(/^https:\/\/allegro\.pl\/(oferta|produkt)\//)) {
return;
}
// Funkcja do pobrania ID oferty z URL
function getOfferIdFromUrl() {
const url = window.location.href;
const match = url.match(/offerId=(\d+)/);
if (match) {
return match[1];
}
return null;
}
// Funkcja do pobrania ID oferty z body JSON
function getOfferIdFromBody() {
const match = document.body.innerHTML.match(/"offerId":"(\d+)"/);
if (match) {
return match[1];
}
return null;
}
// Funkcja do pobrania ID oferty z meta description
function getOfferIdFromMetaDescription() {
const metaDescription = document.querySelector('meta[name="description"]');
if (metaDescription) {
const content = metaDescription.getAttribute('content');
const match = content.match(/Oferta (\d+)$/);
if (match) {
return match[1];
}
}
return null;
}
// Funkcja do pobrania ID oferty z canonical link
function getOfferIdFromCanonical() {
const canonicalLink = document.querySelector('link[rel="canonical"]');
if (canonicalLink) {
const href = canonicalLink.getAttribute('href');
const match = href.match(/-(\d+)$/);
if (match) {
return match[1];
}
}
return null;
}
// Funkcja do pobrania ID oferty (próbuje wszystkie metody)
function getOfferId() {
return getOfferIdFromUrl() ||
getOfferIdFromBody() ||
getOfferIdFromMetaDescription() ||
getOfferIdFromCanonical();
}
// Funkcja do kopiowania ID do schowka (używamy tej samej co wcześniej)
function copyToClipboard(text) {
if (typeof GM_setClipboard !== 'undefined') {
GM_setClipboard(text);
showCopyNotification('ID skopiowane do schowka!');
} else {
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(text).then(function() {
showCopyNotification('ID skopiowane do schowka!');
}).catch(function(err) {
console.error('Błąd kopiowania do schowka:', err);
fallbackCopyTextToClipboard(text);
});
} else {
fallbackCopyTextToClipboard(text);
}
}
}
// Fallback funkcja kopiowania
function fallbackCopyTextToClipboard(text) {
var textArea = document.createElement("textarea");
textArea.value = text;
textArea.style.top = "0";
textArea.style.left = "0";
textArea.style.position = "fixed";
textArea.style.opacity = "0";
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
var successful = document.execCommand('copy');
if (successful) {
showCopyNotification('ID skopiowane do schowka!');
} else {
showCopyNotification('Nie udało się skopiować ID', 'error');
}
} catch (err) {
console.error('Błąd kopiowania:', err);
showCopyNotification('Nie udało się skopiować ID', 'error');
}
document.body.removeChild(textArea);
}
// Funkcja do wyświetlania powiadomienia
function showCopyNotification(message, type) {
var existingNotification = document.querySelector('.copy-notification');
if (existingNotification) {
existingNotification.remove();
}
var notification = document.createElement('div');
notification.className = 'copy-notification';
notification.textContent = message;
notification.style.position = 'fixed';
notification.style.top = '20px';
notification.style.right = '20px';
notification.style.backgroundColor = type === 'error' ? '#dc3545' : '#28a745';
notification.style.color = '#fff';
notification.style.padding = '12px 20px';
notification.style.borderRadius = '5px';
notification.style.zIndex = '10001';
notification.style.fontSize = '14px';
notification.style.fontWeight = 'bold';
notification.style.boxShadow = '0 2px 10px rgba(0,0,0,0.3)';
notification.style.transition = 'opacity 0.3s ease';
document.body.appendChild(notification);
setTimeout(function() {
notification.style.opacity = '0';
setTimeout(function() {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
}, 300);
}, 3000);
}
// Funkcja do otwierania strony edycji oferty
function openEditPage(offerId) {
if (!offerId) {
showCopyNotification('Nie udało się pobrać ID oferty', 'error');
return;
}
const editUrl = `https://salescenter.allegro.com/offer/${offerId}`;
window.open(editUrl, '_blank');
showCopyNotification('Otwieranie strony edycji oferty...', 'success');
}
// Funkcja do tworzenia przycisków na stronie oferty
function createOfferButtons() {
// Sprawdzamy czy przyciski już istnieją
if (document.querySelector('.offer-edytuj-btn') || document.querySelector('.offer-copy-id-btn')) {
return;
}
const offerId = getOfferId();
if (!offerId) {
console.log('Nie udało się pobrać ID oferty');
return;
}
// Znajdujemy kontener na przyciski (np. nagłówek oferty)
const targetContainer = document.querySelector('[data-role="offer-header"]') ||
document.querySelector('.offer-header') ||
document.querySelector('h1') ||
document.querySelector('.offer-title') ||
document.body;
if (!targetContainer) {
console.log('Nie znaleziono kontenera na przyciski');
return;
}
// Tworzymy kontener na przyciski
const buttonContainer = document.createElement('div');
buttonContainer.style.position = 'relative';
buttonContainer.style.marginTop = '10px';
buttonContainer.style.marginBottom = '10px';
buttonContainer.style.display = 'flex';
buttonContainer.style.gap = '10px';
buttonContainer.style.flexWrap = 'wrap';
// Przycisk Edytuj (jeśli funkcja włączona)
if (window.vSprintPlusSettings && window.vSprintPlusSettings.edytuj !== false) {
const edytujButton = document.createElement('button');
edytujButton.className = 'offer-edytuj-btn';
edytujButton.innerHTML = 'Edytuj';
edytujButton.style.padding = '10px 16px';
edytujButton.style.backgroundColor = '#6c757d';
edytujButton.style.color = '#fff';
edytujButton.style.border = 'none';
edytujButton.style.borderRadius = '5px';
edytujButton.style.cursor = 'pointer';
edytujButton.style.fontSize = '14px';
edytujButton.style.fontWeight = 'bold';
edytujButton.style.transition = 'background 0.2s';
edytujButton.onmouseenter = function() {
edytujButton.style.backgroundColor = '#5a6268';
};
edytujButton.onmouseleave = function() {
edytujButton.style.backgroundColor = '#6c757d';
};
edytujButton.onclick = function(event) {
event.preventDefault();
openEditPage(offerId);
};
buttonContainer.appendChild(edytujButton);
}
// Przycisk Kopiuj ID (jeśli funkcja włączona)
if (window.vSprintPlusSettings && window.vSprintPlusSettings.kopiujId !== false) {
const copyIdButton = document.createElement('button');
copyIdButton.className = 'offer-copy-id-btn';
copyIdButton.innerHTML = 'Kopiuj ID';
copyIdButton.style.padding = '10px 16px';
copyIdButton.style.backgroundColor = '#28a745';
copyIdButton.style.color = '#fff';
copyIdButton.style.border = 'none';
copyIdButton.style.borderRadius = '5px';
copyIdButton.style.cursor = 'pointer';
copyIdButton.style.fontSize = '14px';
copyIdButton.style.fontWeight = 'bold';
copyIdButton.style.transition = 'background 0.2s';
copyIdButton.onmouseenter = function() {
copyIdButton.style.backgroundColor = '#218838';
};
copyIdButton.onmouseleave = function() {
copyIdButton.style.backgroundColor = '#28a745';
};
copyIdButton.onclick = function(event) {
event.preventDefault();
copyToClipboard(offerId);
};
buttonContainer.appendChild(copyIdButton);
}
// Dodajemy kontener do strony (po elemencie, nie jako dziecko)
if (targetContainer.tagName === 'H1' || targetContainer.tagName === 'BODY') {
targetContainer.insertAdjacentElement('afterend', buttonContainer);
} else {
targetContainer.appendChild(buttonContainer);
}
console.log('Przyciski oferty dodane, ID:', offerId);
}
// Inicjalizacja - czekamy na załadowanie strony
let attempts = 0;
const maxAttempts = 10;
function tryCreateButtons() {
attempts++;
const offerId = getOfferId();
const h1 = document.querySelector('h1');
if (offerId && h1) {
createOfferButtons();
return true;
}
if (attempts < maxAttempts) {
setTimeout(tryCreateButtons, 500);
}
return false;
}
function init() {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', tryCreateButtons);
} else {
tryCreateButtons();
}
const observer = new MutationObserver(function(mutations) {
if (document.querySelector('.offer-edytuj-btn') || document.querySelector('.offer-copy-id-btn')) {
return;
}
if (document.querySelector('h1') && getOfferId()) {
createOfferButtons();
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
init();
})();
// --- COSTS SUMMARY TRACKER ---
(function() {
'use strict';
if (!window.location.href.match(/^https:\/\/salescenter\.allegro\.com\/costs-summary/)) {
return;
}
const STORAGE_KEY = 'vSprintPlusCostsData';
const THRESHOLD_KEY = 'vSprintPlusMaxFeeThreshold';
function getAccountThreshold(account) {
const thresholds = JSON.parse(localStorage.getItem(THRESHOLD_KEY) || '{}');
return thresholds[account] || null;
}
function setAccountThreshold(account, threshold) {
const thresholds = JSON.parse(localStorage.getItem(THRESHOLD_KEY) || '{}');
if (threshold === null || threshold === '') {
delete thresholds[account];
} else {
thresholds[account] = parseFloat(threshold);
}
localStorage.setItem(THRESHOLD_KEY, JSON.stringify(thresholds));
}
function parseAmount(text) {
if (!text) return 0;
const clean = text.replace(/[^\d,.-]/g, '').replace(/\s/g, '').replace(',', '.');
return parseFloat(clean) || 0;
}
function getAccountName() {
const accountSpan = document.querySelector('div[data-testid="accountInfo"] span');
return accountSpan ? accountSpan.textContent.trim() : 'unknown';
}
function getDateKey() {
const dateInput = document.querySelector('input#Data\\ do');
if (dateInput && dateInput.value) {
const value = dateInput.value;
const parts = value.split(' ');
if (parts.length === 3) {
const day = parts[0];
const monthNames = ['stycznia', 'lutego', 'marca', 'kwietnia', 'maja', 'czerwca', 'lipca', 'sierpnia', 'września', 'października', 'listopada', 'grudnia'];
const monthIndex = monthNames.indexOf(parts[1].toLowerCase());
const year = parts[2];
if (monthIndex !== -1) {
return `${year}-${String(monthIndex + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
}
}
const dotParts = value.split('.');
if (dotParts.length === 3) {
return `${dotParts[2]}-${dotParts[1]}-${dotParts[0]}`;
}
}
const now = new Date();
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
}
function extractCostsData() {
const data = {
date: getDateKey(),
account: getAccountName(),
timestamp: Date.now(),
details: {}
};
const summarySection = Array.from(document.querySelectorAll('h2')).find(h => h.textContent.includes('Podsumowanie'));
if (summarySection) {
const container = summarySection.closest('.msts_pt') || summarySection.parentElement;
const rows = container.querySelectorAll('.m7f5_sf');
rows.forEach(row => {
const labelEl = row.querySelector('.mzaq_56 strong, .mzaq_56 span');
const valueEl = row.querySelector('.mzmg_f9 span[lang]');
if (labelEl && valueEl) {
const label = labelEl.textContent.trim();
const value = parseAmount(valueEl.textContent);
if (label.includes('Wartość sprzedaży') && label.includes('dostawy')) {
data.salesAndDelivery = value;
} else if (label === 'Opłaty') {
data.totalFees = value;
} else if (label === 'Obowiązkowe') {
data.mandatoryFees = value;
} else if (label === 'Dostawa') {
data.deliveryFees = value;
} else if (label.includes('Reklama') || label.includes('promowanie')) {
data.advertisingFees = value;
} else if (label === 'Abonament') {
data.subscriptionFees = value;
}
}
});
}
const allLabelEls = document.querySelectorAll('[data-testid$="-table-label-name"]');
allLabelEls.forEach(labelEl => {
const testId = labelEl.getAttribute('data-testid');
const label = labelEl.textContent.trim();
const prefix = testId.replace('-table-label-name', '');
const valueEl = document.querySelector(`[data-testid="${prefix}-table-label-value"]`);
if (valueEl) {
const value = parseAmount(valueEl.textContent);
if (value !== 0 && !label.includes('Wartość sprzedaży')) {
data.details[label] = value;
}
}
});
console.log('Extracted data:', data);
return data;
}
function checkDataExists(account, date) {
try {
const allData = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');
return allData[account] && allData[account][date];
} catch (e) {
return false;
}
}
function saveData(data, forceOverwrite = false, silent = false) {
if (!data || !data.account) return false;
let allData = {};
try {
allData = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');
} catch (e) {}
if (!forceOverwrite && allData[data.account] && allData[data.account][data.date]) {
if (!silent) showNotification('Dane z ' + data.date + ' już istnieją', 'warning');
return false;
}
if (!allData[data.account]) {
allData[data.account] = {};
}
allData[data.account][data.date] = {
salesAndDelivery: data.salesAndDelivery || 0,
totalFees: data.totalFees || 0,
mandatoryFees: data.mandatoryFees || 0,
deliveryFees: data.deliveryFees || 0,
advertisingFees: data.advertisingFees || 0,
subscriptionFees: data.subscriptionFees || 0,
details: data.details || {},
timestamp: data.timestamp
};
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(allData));
if (!silent) showNotification('Dane zapisane: ' + data.date);
return true;
} catch (e) {
showNotification('Błąd zapisu danych', 'error');
return false;
}
}
function showNotification(message, type = 'success') {
const notification = document.createElement('div');
notification.textContent = message;
const colors = {
success: '#28a745',
error: '#dc3545',
warning: '#ffc107'
};
notification.style.cssText = `
position: fixed;
top: 80px;
right: 20px;
padding: 12px 20px;
background: ${colors[type] || colors.success};
color: ${type === 'warning' ? '#333' : 'white'};
border-radius: 5px;
font-size: 14px;
font-weight: bold;
z-index: 99999;
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
`;
document.body.appendChild(notification);
setTimeout(() => notification.remove(), 3000);
}
function showThresholdModal(account, currentValue, onSave) {
const existing = document.getElementById('vSprint-threshold-modal');
if (existing) existing.remove();
const modal = document.createElement('div');
modal.id = 'vSprint-threshold-modal';
modal.style.cssText = `
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.5);
z-index: 200000;
display: flex;
align-items: center;
justify-content: center;
`;
modal.innerHTML = `
<div style="background: #fff; border-radius: 8px; padding: 24px; width: 360px; box-shadow: 0 4px 20px rgba(0,0,0,0.3);">
<h3 style="margin: 0 0 16px 0; font-size: 16px; color: #333;">⚙️ Maksymalny próg kosztów</h3>
<p style="margin: 0 0 12px 0; font-size: 13px; color: #666;">Konto: <strong>${account}</strong></p>
<input type="number" id="vSprint-threshold-input" value="${currentValue || ''}" min="0" max="100" step="0.1" placeholder="np. 25" style="width: 100%; padding: 10px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 16px; box-sizing: border-box;">
<p style="margin: 8px 0 0 0; font-size: 11px; color: #999;">Wpisz wartość 0-100%. Zostaw puste aby usunąć próg.</p>
<div style="margin-top: 20px; display: flex; gap: 10px; justify-content: flex-end;">
<button id="vSprint-threshold-cancel" style="padding: 8px 16px; background: #f0f0f0; border: none; border-radius: 4px; cursor: pointer; font-size: 14px;">Anuluj</button>
<button id="vSprint-threshold-save" style="padding: 8px 20px; background: #ff5a00; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; font-weight: 500;">Zapisz</button>
</div>
</div>
`;
document.body.appendChild(modal);
const input = document.getElementById('vSprint-threshold-input');
input.focus();
input.select();
const close = () => modal.remove();
modal.onclick = (e) => { if (e.target === modal) close(); };
document.getElementById('vSprint-threshold-cancel').onclick = close;
document.getElementById('vSprint-threshold-save').onclick = () => {
const val = input.value.trim();
onSave(val === '' ? null : parseFloat(val));
close();
};
input.onkeydown = (e) => {
if (e.key === 'Enter') document.getElementById('vSprint-threshold-save').click();
if (e.key === 'Escape') close();
};
}
function formatCurrency(amount) {
return new Intl.NumberFormat('pl-PL', { style: 'currency', currency: 'PLN' }).format(amount);
}
function formatDate(dateStr) {
const parts = dateStr.split('-');
return `${parts[2]}.${parts[1]}`;
}
function createDetailedDataPanel(date, account, details, summaryHtml = '') {
const existingDetail = document.getElementById('vSprint-detail-panel');
if (existingDetail) existingDetail.remove();
const panel = document.createElement('div');
panel.id = 'vSprint-detail-panel';
panel.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 750px;
max-height: 85vh;
overflow-y: auto;
background: #fff;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
z-index: 100000;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
`;
let detailRows = '';
const sortedDetails = Object.entries(details).sort((a, b) => a[0].localeCompare(b[0]));
sortedDetails.forEach(([label, value]) => {
const color = value < 0 ? '#dc3545' : '#28a745';
detailRows += `
<tr>
<td style="padding: 10px 12px; border-bottom: 1px solid #eee;">${label}</td>
<td style="padding: 10px 12px; border-bottom: 1px solid #eee; text-align: right; color: ${color}; font-weight: 500;">${formatCurrency(value)}</td>
</tr>
`;
});
panel.innerHTML = `
<div style="background: #ff5a00; color: white; padding: 18px 20px; border-radius: 8px 8px 0 0; display: flex; justify-content: space-between; align-items: center;">
<h3 style="margin: 0; font-size: 18px;">📋 ${formatDate(date)}</h3>
<button id="vSprint-close-detail" style="background: none; border: none; color: white; font-size: 24px; cursor: pointer;">✕</button>
</div>
<div style="padding: 20px;">
<p style="margin: 0 0 15px 0; color: #666; font-size: 14px;">Konto: <strong>${account}</strong></p>
${summaryHtml}
<table style="width: 100%; border-collapse: collapse; font-size: 14px;">
<thead>
<tr style="background: #f5f5f5;">
<th style="padding: 12px; text-align: left; border-bottom: 2px solid #ddd;">Pozycja</th>
<th style="padding: 12px; text-align: right; border-bottom: 2px solid #ddd;">Kwota</th>
</tr>
</thead>
<tbody>
${detailRows}
</tbody>
</table>
</div>
`;
document.body.appendChild(panel);
document.getElementById('vSprint-close-detail').onclick = () => panel.remove();
panel.onclick = (e) => {
if (e.target === panel) panel.remove();
};
}
function getMonthName(monthNum) {
const months = ['Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec',
'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień'];
return months[monthNum - 1] || '';
}
function deleteReport(account, date) {
let allData = {};
try {
allData = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');
} catch (e) { return; }
if (allData[account] && allData[account][date]) {
delete allData[account][date];
localStorage.setItem(STORAGE_KEY, JSON.stringify(allData));
showNotification('Usunięto: ' + formatDate(date));
}
}
function createPanel(showAllAccounts = false) {
const existingPanel = document.getElementById('vSprint-costs-panel');
if (existingPanel) {
existingPanel.remove();
return;
}
const panel = document.createElement('div');
panel.id = 'vSprint-costs-panel';
panel.style.cssText = `
position: fixed;
top: 80px;
right: 20px;
width: 720px;
max-height: 85vh;
overflow-y: auto;
background: #fff;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
z-index: 99998;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
`;
let allData = {};
try {
allData = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');
} catch (e) {}
const currentAccount = getAccountName();
const allAccounts = Object.keys(allData);
const accountsToShow = showAllAccounts ? allAccounts : allAccounts.filter(a => a === currentAccount);
let html = `
<div style="background: #ff5a00; color: white; padding: 18px 20px; border-radius: 8px 8px 0 0; display: flex; justify-content: space-between; align-items: center;">
<h3 style="margin: 0; font-size: 18px;">📊 Historia kosztów</h3>
<div style="display: flex; gap: 10px; align-items: center;">
<button id="vSprint-toggle-accounts" style="background: rgba(255,255,255,0.2); color: white; border: none; border-radius: 4px; padding: 6px 12px; cursor: pointer; font-size: 12px;">${showAllAccounts ? 'Pokaż moje' : 'Wszystkie konta'}</button>
<button id="vSprint-close-panel" style="background: none; border: none; color: white; font-size: 24px; cursor: pointer;">✕</button>
</div>
</div>
<div style="padding: 20px;">
`;
if (accountsToShow.length === 0) {
html += '<p style="color: #666; text-align: center; font-size: 14px;">Brak zapisanych danych' + (!showAllAccounts && allAccounts.length > 0 ? ' dla tego konta' : '') + '</p>';
} else {
accountsToShow.forEach(account => {
const accountData = allData[account];
const allDates = Object.keys(accountData).sort().reverse();
const groupedByMonth = {};
allDates.forEach(date => {
const [year, month] = date.split('-');
const key = `${year}-${month}`;
if (!groupedByMonth[key]) {
groupedByMonth[key] = { year, month, dates: [] };
}
groupedByMonth[key].dates.push(date);
});
const accountThreshold = getAccountThreshold(account);
html += `
<div style="margin-bottom: 25px;">
<div style="margin: 0 0 12px 0; padding-bottom: 8px; border-bottom: 2px solid #ff5a00; display: flex; justify-content: space-between; align-items: center;">
<h4 style="margin: 0; color: #333; font-size: 16px;">👤 ${account}${account === currentAccount ? ' (obecne)' : ''}</h4>
<div style="display: flex; align-items: center; gap: 8px;">
${accountThreshold !== null ? `<span style="background: #28a745; color: white; padding: 3px 8px; border-radius: 4px; font-size: 11px; font-weight: 600;">max: ${accountThreshold}%</span>` : ''}
<button class="vSprint-threshold-btn" data-account="${account}" style="background: none; color: #ff5a00; border: 1px solid #ff5a00; border-radius: 4px; padding: 3px 8px; cursor: pointer; font-size: 11px; font-weight: 500;">⚙️ próg</button>
</div>
</div>
`;
Object.entries(groupedByMonth).forEach(([monthKey, monthData]) => {
html += `
<div style="margin-bottom: 15px;">
<div style="background: #f8f9fa; padding: 8px 12px; border-radius: 4px; margin-bottom: 8px; font-weight: 500; color: #495057; font-size: 13px;">
📅 ${getMonthName(parseInt(monthData.month))} ${monthData.year}
</div>
<table style="width: 100%; border-collapse: collapse; font-size: 13px;">
<thead>
<tr style="background: #f5f5f5;">
<th style="padding: 8px; text-align: left; border-bottom: 2px solid #ddd;">Data</th>
<th style="padding: 8px; text-align: right; border-bottom: 2px solid #ddd;">Sprzedaż</th>
<th style="padding: 8px; text-align: right; border-bottom: 2px solid #ddd;">Opłaty</th>
<th style="padding: 8px; text-align: right; border-bottom: 2px solid #ddd;">%</th>
<th style="padding: 8px; text-align: center; border-bottom: 2px solid #ddd; width: 100px;"></th>
</tr>
</thead>
<tbody>
`;
monthData.dates.forEach((date, indexInMonth) => {
const d = accountData[date];
const feePercent = d.salesAndDelivery > 0 ? ((Math.abs(d.totalFees) / d.salesAndDelivery) * 100).toFixed(1) : '0';
const globalIndex = allDates.indexOf(date);
if (globalIndex < allDates.length - 1) {
const prevDate = allDates[globalIndex + 1];
const prevData = accountData[prevDate];
const salesDiff = prevData.salesAndDelivery > 0
? ((d.salesAndDelivery - prevData.salesAndDelivery) / prevData.salesAndDelivery * 100).toFixed(1)
: null;
const feesDiff = Math.abs(prevData.totalFees) > 0
? ((Math.abs(d.totalFees) - Math.abs(prevData.totalFees)) / Math.abs(prevData.totalFees) * 100).toFixed(1)
: null;
const prevFeePercent = prevData.salesAndDelivery > 0
? ((Math.abs(prevData.totalFees) / prevData.salesAndDelivery) * 100).toFixed(1)
: '0';
const feePercentDiff = (parseFloat(feePercent) - parseFloat(prevFeePercent)).toFixed(1);
const salesDiffDisplay = salesDiff !== null ? (parseFloat(salesDiff) >= 0 ? `+${salesDiff}%` : `${salesDiff}%`) : '';
const feesDiffDisplay = feesDiff !== null ? (parseFloat(feesDiff) >= 0 ? `+${feesDiff}%` : `${feesDiff}%`) : '';
const feePercentDiffDisplay = parseFloat(feePercentDiff) >= 0 ? `+${feePercentDiff}` : feePercentDiff;
const salesColor = parseFloat(salesDiff) >= 0 ? '#28a745' : '#dc3545';
const feesColor = parseFloat(feesDiff) >= 0 ? '#dc3545' : '#28a745';
const feePercentColor = parseFloat(feePercentDiff) >= 0 ? '#dc3545' : '#28a745';
const salesCell = `<span style="color: #28a745; font-weight: 500;">${formatCurrency(d.salesAndDelivery)}</span>${salesDiffDisplay ? `<br><small style="font-size: 10px; color: ${salesColor};" title="vs ${formatDate(prevDate)}">${salesDiffDisplay}</small>` : ''}`;
const feesCell = `<span style="color: #dc3545; font-weight: 500;">${formatCurrency(d.totalFees)}</span>${feesDiffDisplay ? `<br><small style="font-size: 10px; color: ${feesColor};" title="vs ${formatDate(prevDate)}">${feesDiffDisplay}</small>` : ''}`;
const percentCell = `<span style="font-weight: bold;">${feePercent}%</span><br><small style="font-size: 10px; color: ${feePercentColor};" title="vs ${formatDate(prevDate)}">${feePercentDiffDisplay}pp</small>`;
html += `
<tr>
<td style="padding: 8px; border-bottom: 1px solid #eee;">${formatDate(date)}</td>
<td style="padding: 8px; text-align: right; border-bottom: 1px solid #eee;">${salesCell}</td>
<td style="padding: 8px; text-align: right; border-bottom: 1px solid #eee;">${feesCell}</td>
<td style="padding: 8px; text-align: right; border-bottom: 1px solid #eee;">${percentCell}</td>
<td style="padding: 8px; text-align: center; border-bottom: 1px solid #eee; white-space: nowrap;">
<button class="vSprint-more-btn" data-date="${date}" data-account="${account}" style="background: none; color: #ff5a00; border: none; cursor: pointer; font-size: 11px; text-decoration: underline; padding: 2px 4px;">Więcej</button>
<button class="vSprint-delete-btn" data-date="${date}" data-account="${account}" style="background: none; color: #999; border: none; cursor: pointer; font-size: 11px; padding: 2px 4px;" title="Usuń ten raport">🗑️</button>
</td>
</tr>
`;
} else {
html += `
<tr>
<td style="padding: 8px; border-bottom: 1px solid #eee;">${formatDate(date)}</td>
<td style="padding: 8px; text-align: right; border-bottom: 1px solid #eee; color: #28a745; font-weight: 500;">${formatCurrency(d.salesAndDelivery)}</td>
<td style="padding: 8px; text-align: right; border-bottom: 1px solid #eee; color: #dc3545; font-weight: 500;">${formatCurrency(d.totalFees)}</td>
<td style="padding: 8px; text-align: right; border-bottom: 1px solid #eee; font-weight: bold;">${feePercent}%</td>
<td style="padding: 8px; text-align: center; border-bottom: 1px solid #eee; white-space: nowrap;">
<button class="vSprint-more-btn" data-date="${date}" data-account="${account}" style="background: none; color: #ff5a00; border: none; cursor: pointer; font-size: 11px; text-decoration: underline; padding: 2px 4px;">Więcej</button>
<button class="vSprint-delete-btn" data-date="${date}" data-account="${account}" style="background: none; color: #999; border: none; cursor: pointer; font-size: 11px; padding: 2px 4px;" title="Usuń ten raport">🗑️</button>
</td>
</tr>
`;
}
});
html += `
</tbody>
</table>
</div>
`;
});
html += `</div>`;
});
}
html += `
`;
panel.innerHTML = html;
document.body.appendChild(panel);
document.getElementById('vSprint-close-panel').onclick = () => panel.remove();
document.getElementById('vSprint-toggle-accounts').onclick = () => {
panel.remove();
createPanel(!showAllAccounts);
};
panel.querySelectorAll('.vSprint-more-btn').forEach(btn => {
btn.onclick = (e) => {
e.stopPropagation();
const date = btn.dataset.date;
const account = btn.dataset.account;
const d = allData[account]?.[date];
if (!d) return;
const details = d.details || {};
const summaryHtml = `
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 15px; padding-bottom: 15px; border-bottom: 1px solid #eee;">
<span style="font-weight: 500;">Obowiązkowe:</span><span style="text-align: right; color: #dc3545;">${formatCurrency(d.mandatoryFees)}</span>
<span style="font-weight: 500;">Dostawa:</span><span style="text-align: right; color: #dc3545;">${formatCurrency(d.deliveryFees)}</span>
<span style="font-weight: 500;">Reklama:</span><span style="text-align: right; color: #dc3545;">${formatCurrency(d.advertisingFees)}</span>
<span style="font-weight: 500;">Abonament:</span><span style="text-align: right; color: #dc3545;">${formatCurrency(d.subscriptionFees)}</span>
</div>
`;
createDetailedDataPanel(date, account, details, summaryHtml);
};
});
panel.querySelectorAll('.vSprint-delete-btn').forEach(btn => {
btn.onclick = (e) => {
e.stopPropagation();
const date = btn.dataset.date;
const account = btn.dataset.account;
if (confirm(`Usunąć raport z ${formatDate(date)}?`)) {
deleteReport(account, date);
panel.remove();
createPanel(showAllAccounts);
}
};
});
panel.querySelectorAll('.vSprint-threshold-btn').forEach(btn => {
btn.onclick = (e) => {
e.stopPropagation();
const account = btn.dataset.account;
const currentVal = getAccountThreshold(account);
showThresholdModal(account, currentVal, (newThreshold) => {
if (newThreshold === null) {
setAccountThreshold(account, null);
showNotification('Usunięto próg kosztów');
} else if (isNaN(newThreshold) || newThreshold < 0 || newThreshold > 100) {
showNotification('Nieprawidłowa wartość (0-100)', 'error');
return;
} else {
setAccountThreshold(account, newThreshold);
showNotification(`Ustawiono próg: ${newThreshold}%`);
}
panel.remove();
createPanel(showAllAccounts);
});
};
});
}
function createSaveButton() {
if (document.getElementById('vSprint-save-btn')) return;
const downloadIcon = document.querySelector('span[data-testid="download-report-button"]');
const downloadBtn = downloadIcon ? downloadIcon.closest('button') : null;
if (!downloadBtn) {
const btns = document.querySelectorAll('button');
for (const btn of btns) {
if (btn.textContent.includes('Pobierz raport')) {
return createSaveButtonNextTo(btn);
}
}
return;
}
createSaveButtonNextTo(downloadBtn);
}
function createSaveButtonNextTo(downloadBtn) {
if (document.getElementById('vSprint-save-btn')) return;
const saveBtn = document.createElement('button');
saveBtn.id = 'vSprint-save-btn';
saveBtn.type = 'button';
saveBtn.innerHTML = '💾 Zapamiętaj';
saveBtn.style.cssText = `
margin-right: 8px;
padding: 0 12px;
height: 32px;
background: #ff5a00;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 13px;
font-weight: 500;
display: inline-flex;
align-items: center;
gap: 4px;
transition: background 0.2s;
white-space: nowrap;
`;
saveBtn.onmouseenter = () => saveBtn.style.background = '#e64d00';
saveBtn.onmouseleave = () => saveBtn.style.background = '#ff5a00';
saveBtn.onclick = () => {
const data = extractCostsData();
if (data && data.salesAndDelivery) {
const exists = checkDataExists(data.account, data.date);
if (exists) {
if (confirm('Dane z ' + data.date + ' już istnieją. Czy chcesz je nadpisać?')) {
saveData(data, true);
}
} else {
saveData(data);
}
} else {
showNotification('Nie udało się pobrać danych', 'error');
}
};
downloadBtn.insertAdjacentElement('beforebegin', saveBtn);
}
function createFloatingButton() {
if (document.getElementById('vSprint-costs-btn')) return;
const btn = document.createElement('button');
btn.id = 'vSprint-costs-btn';
btn.innerHTML = '📊 Koszty';
btn.style.cssText = `
position: fixed;
top: 80px;
right: 20px;
padding: 10px 16px;
background: #ff5a00;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
z-index: 99997;
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
transition: background 0.2s;
`;
btn.onmouseenter = () => btn.style.background = '#e64d00';
btn.onmouseleave = () => btn.style.background = '#ff5a00';
btn.onclick = () => createPanel(false);
document.body.appendChild(btn);
}
function showComparisonOnPage() {
const allData = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');
const currentAccount = getAccountName();
const accountData = allData[currentAccount];
const currentDate = getDateKey();
const currentData = accountData ? accountData[currentDate] : null;
const summarySection = Array.from(document.querySelectorAll('h2')).find(h => h.textContent.includes('Podsumowanie'));
if (!summarySection) return;
const container = summarySection.closest('.msts_pt') || summarySection.parentElement;
const rows = container.querySelectorAll('.m7f5_sf');
const maxThreshold = getAccountThreshold(currentAccount);
rows.forEach(row => {
const labelEl = row.querySelector('.mzaq_56 strong, .mzaq_56 span');
if (!labelEl) return;
const label = labelEl.textContent.trim();
const percentContainer = row.querySelector('.munh_8');
if (!percentContainer) return;
if (percentContainer.dataset.vsprintInit === '1') {
if (!percentContainer.querySelector('.vSprint-threshold-badge') && !percentContainer.querySelector('.vSprint-set-threshold')) {
delete percentContainer.dataset.vsprintInit;
} else {
return;
}
}
percentContainer.dataset.vsprintInit = '1';
percentContainer.style.position = 'relative';
if (label === 'Opłaty') {
const percentSpan = percentContainer.querySelector('span.mgn2_14');
const liveData = extractCostsData();
const currentFeePercent = liveData && liveData.salesAndDelivery > 0
? (Math.abs(liveData.totalFees) / liveData.salesAndDelivery * 100)
: null;
if (maxThreshold !== null) {
const overThreshold = currentFeePercent !== null && currentFeePercent > maxThreshold;
if (overThreshold && percentSpan) {
percentSpan.style.background = 'rgba(220, 53, 69, 0.2)';
percentSpan.style.padding = '4px 8px';
percentSpan.style.borderRadius = '4px';
percentSpan.style.border = '1px solid rgba(220, 53, 69, 0.4)';
}
const thresholdInfo = document.createElement('span');
thresholdInfo.className = 'vSprint-threshold-badge';
thresholdInfo.style.cssText = `
position: absolute;
left: -9px;
top: 23px;
padding: 4px 8px;
background: ${overThreshold ? '#dc3545' : '#28a745'};
color: white;
border-radius: 4px;
font-size: 12px;
font-weight: 400;
white-space: nowrap;
cursor: pointer;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
`;
thresholdInfo.innerHTML = `max: ${maxThreshold}%`;
thresholdInfo.title = `Kliknij aby zmienić próg (obecnie: ${maxThreshold}%)`;
percentContainer.appendChild(thresholdInfo);
} else {
const setBadge = document.createElement('span');
setBadge.className = 'vSprint-set-threshold';
setBadge.style.cssText = `
position: absolute;
left: -15px;
top: -23px;
padding: 4px 8px;
background: #17a2b8;
color: white;
border-radius: 4px;
font-size: 12px;
font-weight: 400;
white-space: nowrap;
cursor: pointer;
box-shadow: 0 2px 4px rgba(0,0,0,0.15);
`;
setBadge.innerHTML = '⚙️ ustaw max';
setBadge.title = 'Kliknij aby ustawić maksymalny próg kosztów';
percentContainer.appendChild(setBadge);
}
percentContainer.onclick = (e) => {
e.stopPropagation();
const currentVal = getAccountThreshold(currentAccount);
showThresholdModal(currentAccount, currentVal, (newThreshold) => {
if (newThreshold === null) {
setAccountThreshold(currentAccount, null);
showNotification('Usunięto próg kosztów');
} else if (isNaN(newThreshold) || newThreshold < 0 || newThreshold > 100) {
showNotification('Nieprawidłowa wartość (0-100)', 'error');
return;
} else {
setAccountThreshold(currentAccount, newThreshold);
showNotification(`Ustawiono próg: ${newThreshold}%`);
}
percentContainer.querySelector('.vSprint-threshold-badge')?.remove();
percentContainer.querySelector('.vSprint-set-threshold')?.remove();
delete percentContainer.dataset.vsprintInit;
showComparisonOnPage();
});
};
}
if (!accountData || !currentData) return;
const dates = Object.keys(accountData).sort().reverse();
const currentIndex = dates.indexOf(currentDate);
if (currentIndex === -1) return;
let prevDate, prevData;
if (currentIndex === 0) {
if (dates.length < 2) return;
prevDate = dates[1];
prevData = accountData[prevDate];
} else {
prevDate = dates[0];
prevData = accountData[prevDate];
}
let currentPercent = 0, prevPercent = 0, diff = 0;
const salesAndDelivery = currentData.salesAndDelivery || 1;
const prevSalesAndDelivery = prevData.salesAndDelivery || 1;
if (label.includes('Wartość sprzedaży') && label.includes('dostawy')) {
currentPercent = 100;
prevPercent = 100;
} else if (label === 'Opłaty') {
currentPercent = salesAndDelivery > 0 ? (Math.abs(currentData.totalFees) / salesAndDelivery * 100) : 0;
prevPercent = prevSalesAndDelivery > 0 ? (Math.abs(prevData.totalFees) / prevSalesAndDelivery * 100) : 0;
} else if (label === 'Obowiązkowe') {
currentPercent = salesAndDelivery > 0 ? (Math.abs(currentData.mandatoryFees) / salesAndDelivery * 100) : 0;
prevPercent = prevSalesAndDelivery > 0 ? (Math.abs(prevData.mandatoryFees) / prevSalesAndDelivery * 100) : 0;
} else if (label === 'Dostawa') {
currentPercent = salesAndDelivery > 0 ? (Math.abs(currentData.deliveryFees) / salesAndDelivery * 100) : 0;
prevPercent = prevSalesAndDelivery > 0 ? (Math.abs(prevData.deliveryFees) / prevSalesAndDelivery * 100) : 0;
} else if (label.includes('Reklama') || label.includes('promowanie')) {
currentPercent = salesAndDelivery > 0 ? (Math.abs(currentData.advertisingFees) / salesAndDelivery * 100) : 0;
prevPercent = prevSalesAndDelivery > 0 ? (Math.abs(prevData.advertisingFees) / prevSalesAndDelivery * 100) : 0;
} else if (label === 'Abonament') {
currentPercent = salesAndDelivery > 0 ? (Math.abs(currentData.subscriptionFees) / salesAndDelivery * 100) : 0;
prevPercent = prevSalesAndDelivery > 0 ? (Math.abs(prevData.subscriptionFees) / prevSalesAndDelivery * 100) : 0;
} else {
return;
}
diff = (currentPercent - prevPercent).toFixed(1);
if (Math.abs(diff) < 0.05) return;
if (percentContainer.querySelector('.vSprint-diff-badge')) return;
const diffDisplay = parseFloat(diff) >= 0 ? `+${diff}%` : `${diff}%`;
const color = parseFloat(diff) >= 0 ? '#dc3545' : '#28a745';
const offsetLeft = 12;
const diffBadge = document.createElement('span');
diffBadge.className = 'vSprint-diff-badge';
diffBadge.style.cssText = `
position: absolute;
left: 100%;
top: 50%;
transform: translateY(-50%);
margin-left: ${offsetLeft}px;
padding: 2px 6px;
background: ${color};
color: white;
border-radius: 3px;
font-size: 11px;
font-weight: 500;
cursor: help;
white-space: nowrap;
`;
diffBadge.textContent = diffDisplay;
diffBadge.title = `vs ${formatDate(prevDate)}`;
percentContainer.appendChild(diffBadge);
});
}
function autoSaveData() {
const data = extractCostsData();
if (!data || !data.salesAndDelivery) {
console.log('Auto-save: no data to save');
return;
}
const exists = checkDataExists(data.account, data.date);
if (exists) {
console.log('Auto-save: data already exists for', data.date);
return;
}
if (saveData(data, false, true)) {
console.log('Auto-save: saved successfully');
}
}
function init() {
createFloatingButton();
let attempts = 0;
const maxAttempts = 30;
let autoSaved = false;
function tryCreateSaveButton() {
attempts++;
createSaveButton();
showComparisonOnPage();
if (attempts === 3 && !autoSaved) {
autoSaveData();
autoSaved = true;
}
if (attempts < maxAttempts) {
setTimeout(tryCreateSaveButton, 500);
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', tryCreateSaveButton);
} else {
setTimeout(tryCreateSaveButton, 1000);
}
setInterval(() => {
createSaveButton();
showComparisonOnPage();
}, 2000);
let observerTimeout = null;
const observer = new MutationObserver(() => {
createFloatingButton();
if (observerTimeout) clearTimeout(observerTimeout);
observerTimeout = setTimeout(() => {
createSaveButton();
showComparisonOnPage();
}, 300);
});
observer.observe(document.body, { childList: true, subtree: true });
}
init();
})();