Ozon Product Parser

Парсер товаров с Ozon для анализа поисковой выдачи

Size

238.8 KB

Version

1.8.58

Created

Mar 27, 2026

Updated

20 days ago

1// ==UserScript==
2// @name		Ozon Product Parser
3// @description		Парсер товаров с Ozon для анализа поисковой выдачи
4// @version		1.8.58
5// @match		https://ozon.ru/*
6// @match		https://www.ozon.ru/*
7// @grant		GM.getValue
8// @grant		GM.setValue
9// @grant		GM.deleteValue
10// @icon		https://st.ozone.ru/assets/favicon.ico
11// ==/UserScript==
12(function() {
13    'use strict';
14    console.log('Ozon Product Parser: Extension loaded');
15
16    // Утилита для дебаунса
17    function debounce(func, wait) {
18        let timeout;
19        return function executedFunction(...args) {
20            const later = () => {
21                clearTimeout(timeout);
22                func(...args);
23            };
24            clearTimeout(timeout);
25            timeout = setTimeout(later, wait);
26        };
27    }
28
29    // Добавляем стили
30    function addStyles() {
31        const styles = `
32            .ozon-parser-container {
33                position: fixed;
34                top: 20px;
35                right: 20px;
36                z-index: 10000;
37                display: flex;
38                gap: 10px;
39                cursor: move;
40                user-select: none;
41            }
42            .ozon-parser-container.collapsed {
43                gap: 0;
44            }
45            .ozon-parser-toggle-btn {
46                background: linear-gradient(135deg, #005bff 0%, #0043c7 100%);
47                color: white;
48                border: none;
49                padding: 12px;
50                border-radius: 8px;
51                cursor: pointer;
52                font-size: 18px;
53                font-weight: 600;
54                box-shadow: 0 4px 12px rgba(0, 91, 255, 0.3);
55                transition: all 0.3s ease;
56                width: 44px;
57                height: 44px;
58                display: flex;
59                align-items: center;
60                justify-content: center;
61            }
62            .ozon-parser-toggle-btn:hover {
63                background: linear-gradient(135deg, #0043c7 0%, #002f8f 100%);
64                box-shadow: 0 6px 16px rgba(0, 91, 255, 0.4);
65                transform: translateY(-2px);
66            }
67            .ozon-parser-buttons-wrapper {
68                display: flex;
69                gap: 10px;
70                transition: all 0.3s ease;
71            }
72            .ozon-parser-buttons-wrapper.hidden {
73                display: none;
74            }
75            .ozon-parser-btn {
76                background: linear-gradient(135deg, #005bff 0%, #0043c7 100%);
77                color: white;
78                border: none;
79                padding: 12px 24px;
80                border-radius: 8px;
81                cursor: pointer;
82                font-size: 14px;
83                font-weight: 600;
84                box-shadow: 0 4px 12px rgba(0, 91, 255, 0.3);
85                transition: all 0.3s ease;
86            }
87            .ozon-parser-btn:hover {
88                background: linear-gradient(135deg, #0043c7 0%, #002f8f 100%);
89                box-shadow: 0 6px 16px rgba(0, 91, 255, 0.4);
90                transform: translateY(-2px);
91            }
92            .ozon-parser-btn:active {
93                transform: translateY(0);
94            }
95            .ozon-parser-btn.secondary {
96                background: linear-gradient(135deg, #28a745 0%, #1e7e34 100%);
97                box-shadow: 0 4px 12px rgba(40, 167, 69, 0.3);
98            }
99            .ozon-parser-btn.secondary:hover {
100                background: linear-gradient(135deg, #1e7e34 0%, #155724 100%);
101                box-shadow: 0 6px 16px rgba(40, 167, 69, 0.4);
102            }
103            .ozon-parser-modal {
104                position: fixed;
105                top: 0;
106                left: 0;
107                width: 100%;
108                height: 100%;
109                background: rgba(0, 0, 0, 0.7);
110                display: flex;
111                justify-content: center;
112                align-items: center;
113                z-index: 10001;
114            }
115            .ozon-parser-modal-content {
116                background: white;
117                padding: 30px;
118                border-radius: 12px;
119                max-width: 800px;
120                width: 90%;
121                max-height: 80vh;
122                overflow-y: auto;
123                box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
124            }
125            .ozon-parser-modal-header {
126                font-size: 24px;
127                font-weight: 700;
128                margin-bottom: 20px;
129                color: #333;
130            }
131            .ozon-parser-modal-body {
132                margin-bottom: 20px;
133            }
134            .ozon-parser-textarea {
135                width: 100%;
136                min-height: 200px;
137                padding: 12px;
138                border: 2px solid #e0e0e0;
139                border-radius: 8px;
140                font-size: 14px;
141                font-family: inherit;
142                resize: vertical;
143                box-sizing: border-box;
144            }
145            .ozon-parser-textarea:focus {
146                outline: none;
147                border-color: #005bff;
148            }
149            .ozon-parser-modal-footer {
150                display: flex;
151                gap: 10px;
152                justify-content: flex-end;
153            }
154            .ozon-parser-progress {
155                margin-top: 20px;
156                padding: 15px;
157                background: #f8f9fa;
158                border-radius: 8px;
159                font-size: 14px;
160                color: #333;
161            }
162            .ozon-parser-progress-bar {
163                width: 100%;
164                height: 8px;
165                background: #e0e0e0;
166                border-radius: 4px;
167                margin-top: 10px;
168                overflow: hidden;
169            }
170            .ozon-parser-progress-fill {
171                height: 100%;
172                background: linear-gradient(90deg, #005bff 0%, #0043c7 100%);
173                transition: width 0.3s ease;
174            }
175            .ozon-parser-results-table {
176                width: 100%;
177                border-collapse: collapse;
178                margin-top: 20px;
179                font-size: 13px;
180            }
181            .ozon-parser-results-table th,
182            .ozon-parser-results-table td {
183                padding: 12px;
184                text-align: left;
185                border-bottom: 1px solid #e0e0e0;
186                white-space: nowrap;
187            }
188            .ozon-parser-results-table th {
189                background: #f8f9fa;
190                font-weight: 600;
191                color: #333;
192                position: sticky;
193                top: 0;
194            }
195            .ozon-parser-results-table td:nth-child(3) {
196                max-width: 500px;
197                white-space: normal;
198                word-wrap: break-word;
199            }
200            .ozon-parser-results-table tr:hover {
201                background: #f8f9fa;
202            }
203            .ozon-parser-highlight {
204                background: #fff3cd !important;
205                font-weight: 600;
206            }
207            .ozon-parser-sku-link {
208                color: #005bff;
209                text-decoration: none;
210                font-weight: 600;
211            }
212            .ozon-parser-sku-link:hover {
213                text-decoration: underline;
214            }
215            .ozon-parser-tabs {
216                display: flex;
217                gap: 5px;
218                margin-bottom: 20px;
219                flex-wrap: wrap;
220            }
221            .ozon-parser-tab {
222                padding: 10px 20px;
223                background: #f8f9fa;
224                border: none;
225                border-radius: 6px;
226                cursor: pointer;
227                font-size: 14px;
228                transition: all 0.2s ease;
229            }
230            .ozon-parser-tab:hover {
231                background: #e9ecef;
232            }
233            .ozon-parser-tab.active {
234                background: #005bff;
235                color: white;
236                font-weight: 600;
237            }
238            .ozon-parser-info {
239                padding: 10px;
240                background: #e7f3ff;
241                border-left: 4px solid #005bff;
242                border-radius: 4px;
243                margin-bottom: 15px;
244                font-size: 13px;
245                color: #333;
246            }
247            .ozon-parser-search {
248                width: 100%;
249                padding: 10px 12px;
250                border: 2px solid #e0e0e0;
251                border-radius: 8px;
252                font-size: 14px;
253                margin-bottom: 15px;
254                box-sizing: border-box;
255            }
256            .ozon-parser-search:focus {
257                outline: none;
258                border-color: #005bff;
259            }
260            .ozon-parser-search::placeholder {
261                color: #999;
262            }
263            .ozon-parser-analytics {
264                margin-top: 30px;
265                padding: 20px;
266                background: #f8f9fa;
267                border-radius: 8px;
268            }
269            .ozon-parser-analytics-header {
270                font-size: 20px;
271                font-weight: 700;
272                margin-bottom: 20px;
273                color: #333;
274            }
275            .ozon-parser-analytics-section {
276                margin-bottom: 25px;
277            }
278            .ozon-parser-analytics-section-title {
279                font-size: 16px;
280                font-weight: 600;
281                margin-bottom: 12px;
282                color: #005bff;
283            }
284            .ozon-parser-analytics-grid {
285                display: grid;
286                grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
287                gap: 15px;
288                margin-bottom: 20px;
289            }
290            .ozon-parser-analytics-card {
291                background: white;
292                padding: 15px;
293                border-radius: 8px;
294                border: 1px solid #e0e0e0;
295            }
296            .ozon-parser-analytics-card-title {
297                font-size: 13px;
298                color: #666;
299                margin-bottom: 8px;
300            }
301            .ozon-parser-analytics-card-value {
302                font-size: 24px;
303                font-weight: 700;
304                color: #333;
305            }
306            .ozon-parser-analytics-table {
307                width: 100%;
308                border-collapse: collapse;
309                background: white;
310                border-radius: 8px;
311                overflow: hidden;
312            }
313            .ozon-parser-analytics-table th,
314            .ozon-parser-analytics-table td {
315                padding: 12px;
316                text-align: left;
317                border-bottom: 1px solid #e0e0e0;
318            }
319            .ozon-parser-analytics-table th {
320                background: #f8f9fa;
321                font-weight: 600;
322                color: #333;
323                font-size: 13px;
324            }
325            .ozon-parser-analytics-table td {
326                font-size: 13px;
327            }
328            .ozon-parser-analytics-bar {
329                height: 20px;
330                background: linear-gradient(90deg, #005bff 0%, #0043c7 100%);
331                border-radius: 4px;
332                transition: width 0.3s ease;
333            }
334            .ozon-parser-details-panel {
335                position: fixed;
336                top: 0;
337                right: -600px;
338                width: 600px;
339                height: 100vh;
340                background: white;
341                box-shadow: -4px 0 20px rgba(0, 0, 0, 0.3);
342                z-index: 10002;
343                overflow-y: auto;
344                transition: right 0.3s ease;
345            }
346            .ozon-parser-details-panel.open {
347                right: 0;
348            }
349            .ozon-parser-details-panel-header {
350                position: sticky;
351                top: 0;
352                background: white;
353                padding: 20px;
354                border-bottom: 2px solid #e0e0e0;
355                display: flex;
356                justify-content: space-between;
357                align-items: center;
358                z-index: 1;
359            }
360            .ozon-parser-details-panel-title {
361                font-size: 18px;
362                font-weight: 700;
363                color: #333;
364            }
365            .ozon-parser-details-panel-close {
366                background: #dc3545;
367                color: white;
368                border: none;
369                padding: 8px 16px;
370                border-radius: 6px;
371                cursor: pointer;
372                font-size: 14px;
373                font-weight: 600;
374                transition: all 0.2s ease;
375            }
376            .ozon-parser-details-panel-close:hover {
377                background: #c82333;
378            }
379            .ozon-parser-details-panel-body {
380                padding: 20px;
381            }
382            .ozon-parser-clickable-row {
383                cursor: pointer;
384                transition: background 0.2s ease;
385            }
386            .ozon-parser-clickable-row:hover {
387                background: #e7f3ff !important;
388            }
389        `;
390        const styleElement = document.createElement('style');
391        styleElement.textContent = styles;
392        document.head.appendChild(styleElement);
393        console.log('Ozon Product Parser: Styles added');
394    }
395
396    // Обновляем счетчик высоких цен
397    async function updateHighPriceCounter() {
398        const highPriceBtn = document.querySelector('#high-price-btn');
399        if (!highPriceBtn) return;
400        
401        const highPriceDataJson = await GM.getValue('ozon_parser_high_price', '[]');
402        const highPriceData = JSON.parse(highPriceDataJson);
403        
404        highPriceBtn.textContent = `Высокая цена (${highPriceData.length})`;
405    }
406    
407    // Показываем модальное окно с ценами
408    async function showPricesModal() {
409        console.log('Ozon Product Parser: Opening prices modal');
410        
411        try {
412            // Получаем результаты парсинга
413            const allListResultsJson = await GM.getValue('ozon_parser_list_results', '{}');
414            const allListResults = JSON.parse(allListResultsJson);
415            
416            if (Object.keys(allListResults).length === 0) {
417                alert('Нет данных для анализа цен. Сначала выполните парсинг.');
418                return;
419            }
420            
421            // Загружаем сохраненные привязки SKU к запросам
422            const skuQueryBindingsJson = await GM.getValue('ozon_parser_sku_query_bindings', '{}');
423            const skuQueryBindings = JSON.parse(skuQueryBindingsJson);
424            
425            // Собираем все товары наших брендов из всех запросов
426            const ourProductsData = [];
427            
428            for (const listName in allListResults) {
429                const listData = allListResults[listName];
430                for (const query in listData.queries) {
431                    const products = listData.queries[query];
432                    
433                    // Находим наши товары
434                    const ourProducts = products.filter(p => p.isTargetBrand);
435                    
436                    ourProducts.forEach(ourProduct => {
437                        // Проверяем, есть ли привязка для этого SKU
438                        const boundQuery = skuQueryBindings[ourProduct.sku];
439                        
440                        // Если есть привязка и это не тот запрос - пропускаем
441                        if (boundQuery && boundQuery !== query) {
442                            return;
443                        }
444                        
445                        // Находим топ-3 конкурентов по позиции в выдаче (а не по выручке)
446                        const competitors = products.filter(p => !p.isTargetBrand && p.price > 0);
447                        
448                        // Сортируем конкурентов по позиции в выдаче
449                        const sortedCompetitors = [...competitors].sort((a, b) => a.position - b.position);
450                        
451                        // Берем первых 3 конкурентов по позиции
452                        const top3Competitors = sortedCompetitors.slice(0, 3);
453                        
454                        if (top3Competitors.length > 0) {
455                            // Рассчитываем цены конкурентов пятью способами
456                            const competitorPrices = top3Competitors.map(c => c.price);
457                            const competitorRevenues = top3Competitors.map(c => c.revenue);
458                            
459                            // 1. Максимум по топ-3
460                            const competitorMaxPrice = Math.max(...competitorPrices);
461                            
462                            // 2. Среднее по топ-3
463                            const competitorAvgPrice = competitorPrices.reduce((sum, p) => sum + p, 0) / competitorPrices.length;
464                            
465                            // 3. Средневзвешенное по выручке
466                            const totalRevenue = competitorRevenues.reduce((sum, r) => sum + r, 0);
467                            const competitorWeightedPrice = top3Competitors.reduce((sum, c) => sum + (c.price * c.revenue), 0) / totalRevenue;
468                            
469                            // 4. Минимальная по топ-3
470                            const competitorMinPrice = Math.min(...competitorPrices);
471                            
472                            // 5. Медиана по топ-3
473                            const sortedPrices = [...competitorPrices].sort((a, b) => a - b);
474                            const competitorMedianPrice = sortedPrices[Math.floor(sortedPrices.length / 2)];
475                            
476                            // Рассчитываем среднюю цену (выручка / заказы)
477                            const ourAvgPrice = ourProduct.orders > 0 ? ourProduct.revenue / ourProduct.orders : 0;
478                            const competitorAvgPrices = top3Competitors.map(c => c.orders > 0 ? c.revenue / c.orders : 0);
479                            const competitorAvgPriceValue = competitorAvgPrices.reduce((sum, p) => sum + p, 0) / competitorAvgPrices.length;
480                            
481                            // Рассчитываем выручку конкурентов (среднее)
482                            const competitorRevenue = competitorRevenues.reduce((sum, r) => sum + r, 0) / competitorRevenues.length;
483                            
484                            ourProductsData.push({
485                                sku: ourProduct.sku,
486                                name: ourProduct.name,
487                                query: query,
488                                listName: listName,
489                                
490                                // Текущая цена
491                                ourCurrentPrice: ourProduct.price,
492                                competitorMaxPrice: competitorMaxPrice,
493                                competitorAvgPrice: competitorAvgPrice,
494                                competitorWeightedPrice: competitorWeightedPrice,
495                                competitorMinPrice: competitorMinPrice,
496                                competitorMedianPrice: competitorMedianPrice,
497                                
498                                // Средняя цена
499                                ourAvgPrice: ourAvgPrice,
500                                competitorAvgPriceValue: competitorAvgPriceValue,
501                                
502                                // Выручка
503                                ourRevenue: ourProduct.revenue,
504                                competitorRevenue: competitorRevenue,
505                                
506                                // Для расчета дельт
507                                top3Competitors: top3Competitors,
508                                
509                                // Флаг привязки
510                                isBound: !!boundQuery
511                            });
512                        }
513                    });
514                }
515            }
516            
517            if (ourProductsData.length === 0) {
518                alert('Нет товаров наших брендов в результатах парсинга.');
519                return;
520            }
521            
522            const modal = document.createElement('div');
523            modal.className = 'ozon-parser-modal';
524            
525            const content = document.createElement('div');
526            content.className = 'ozon-parser-modal-content';
527            content.style.maxWidth = '98vw';
528            content.style.width = '98vw';
529            
530            // Состояние сортировки
531            let sortColumn = null;
532            let sortDirection = 'asc';
533            
534            // Функция для экспорта в CSV
535            function exportToCSV(data, method, filters) {
536                // Фильтруем данные так же, как в таблице
537                let filteredData = [...data];
538                
539                // Применяем фильтр по запросу
540                if (filters.querySearch) {
541                    filteredData = filteredData.filter(item => 
542                        item.query.toLowerCase().includes(filters.querySearch.toLowerCase())
543                    );
544                }
545                
546                // Применяем фильтр по SKU
547                if (filters.skuSearch) {
548                    filteredData = filteredData.filter(item => 
549                        item.sku.includes(filters.skuSearch)
550                    );
551                }
552                
553                // Применяем фильтры по выручке
554                if (filters.revenue > 0) {
555                    filteredData = filteredData.filter(item => {
556                        const revenueDelta = ((item.ourRevenue - item.competitorRevenue) / item.competitorRevenue) * 100;
557                        if (filters.revenueDirection === 'less') {
558                            return revenueDelta <= -filters.revenue;
559                        } else {
560                            return revenueDelta >= filters.revenue;
561                        }
562                    });
563                }
564                
565                // Применяем фильтры по текущей цене
566                if (filters.currentPrice > 0) {
567                    filteredData = filteredData.filter(item => {
568                        let competitorPrice;
569                        if (method === 'max') competitorPrice = item.competitorMaxPrice;
570                        else if (method === 'avg') competitorPrice = item.competitorAvgPrice;
571                        else if (method === 'min') competitorPrice = item.competitorMinPrice;
572                        else if (method === 'median') competitorPrice = item.competitorMedianPrice;
573                        else competitorPrice = item.competitorWeightedPrice;
574                        
575                        const priceDelta = ((item.ourCurrentPrice - competitorPrice) / competitorPrice) * 100;
576                        if (filters.currentPriceDirection === 'less') {
577                            return priceDelta <= -filters.currentPrice;
578                        } else {
579                            return priceDelta >= filters.currentPrice;
580                        }
581                    });
582                }
583                
584                // Применяем фильтры по средней цене
585                if (filters.avgPrice > 0) {
586                    filteredData = filteredData.filter(item => {
587                        const avgPriceDelta = ((item.ourAvgPrice - item.competitorAvgPriceValue) / item.competitorAvgPriceValue) * 100;
588                        if (filters.avgPriceDirection === 'less') {
589                            return avgPriceDelta <= -filters.avgPrice;
590                        } else {
591                            return avgPriceDelta >= filters.avgPrice;
592                        }
593                    });
594                }
595                
596                // Формируем CSV
597                const methodNames = {
598                    'max': 'Максимум',
599                    'avg': 'Среднее',
600                    'weighted': 'Средневзвешенное',
601                    'min': 'Минимум',
602                    'median': 'Медиана'
603                };
604                
605                let csv = '\uFEFF'; // BOM для корректного отображения кириллицы в Excel
606                csv += 'SKU;Название;Запрос;Список;';
607                csv += `Наша текущая цена;Конкуренты текущая (${methodNames[method]});Дельта текущей цены (%);`;
608                csv += 'Наша средняя цена;Конкуренты средняя;Дельта средней цены (%);';
609                csv += 'Наша выручка;Конкуренты выручка;Дельта выручки (%)\n';
610                
611                filteredData.forEach(item => {
612                    // Выбираем цену конкурентов в зависимости от метода
613                    let competitorPrice;
614                    if (method === 'max') competitorPrice = item.competitorMaxPrice;
615                    else if (method === 'avg') competitorPrice = item.competitorAvgPrice;
616                    else if (method === 'min') competitorPrice = item.competitorMinPrice;
617                    else if (method === 'median') competitorPrice = item.competitorMedianPrice;
618                    else competitorPrice = item.competitorWeightedPrice;
619                    
620                    // Рассчитываем дельты
621                    const currentPriceDelta = ((item.ourCurrentPrice - competitorPrice) / competitorPrice) * 100;
622                    const avgPriceDelta = ((item.ourAvgPrice - item.competitorAvgPriceValue) / item.competitorAvgPriceValue) * 100;
623                    const revenueDelta = ((item.ourRevenue - item.competitorRevenue) / item.competitorRevenue) * 100;
624                    
625                    // Экранируем кавычки и переносы строк в названии
626                    const safeName = (item.name || '—').replace(/"/g, '""').replace(/\n/g, ' ');
627                    
628                    // Форматируем дельты: добавляем знак = в начале, чтобы Excel интерпретировал как формулу, возвращающую текст
629                    const currentPriceDeltaStr = `="${currentPriceDelta.toFixed(1)}"`;
630                    const avgPriceDeltaStr = `="${avgPriceDelta.toFixed(1)}"`;
631                    const revenueDeltaStr = `="${revenueDelta.toFixed(1)}"`;
632                    
633                    csv += `${item.sku};"${safeName}";${item.query};${item.listName};`;
634                    csv += `${Math.round(item.ourCurrentPrice)};${Math.round(competitorPrice)};${currentPriceDeltaStr};`;
635                    csv += `${Math.round(item.ourAvgPrice)};${Math.round(item.competitorAvgPriceValue)};${avgPriceDeltaStr};`;
636                    csv += `${Math.round(item.ourRevenue)};${Math.round(item.competitorRevenue)};${revenueDeltaStr}\n`;
637                });
638                
639                // Создаем и скачиваем файл
640                const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
641                const link = document.createElement('a');
642                const url = URL.createObjectURL(blob);
643                link.setAttribute('href', url);
644                link.setAttribute('download', `ozon_prices_${new Date().toISOString().slice(0, 10)}.csv`);
645                link.style.visibility = 'hidden';
646                document.body.appendChild(link);
647                link.click();
648                document.body.removeChild(link);
649                
650                console.log(`Ozon Product Parser: Exported ${filteredData.length} products to CSV`);
651            }
652            
653            // Функция для отображения таблицы с учетом выбранного метода и фильтров
654            function displayPricesTable(method, filters) {
655                let filteredData = [...ourProductsData];
656                
657                // Применяем фильтр по запросу
658                if (filters.querySearch) {
659                    filteredData = filteredData.filter(item => 
660                        item.query.toLowerCase().includes(filters.querySearch.toLowerCase())
661                    );
662                }
663                
664                // Применяем фильтр по SKU
665                if (filters.skuSearch) {
666                    filteredData = filteredData.filter(item => 
667                        item.sku.includes(filters.skuSearch)
668                    );
669                }
670                
671                // Применяем фильтры по выручке
672                if (filters.revenue > 0) {
673                    filteredData = filteredData.filter(item => {
674                        const revenueDelta = ((item.ourRevenue - item.competitorRevenue) / item.competitorRevenue) * 100;
675                        if (filters.revenueDirection === 'less') {
676                            return revenueDelta <= -filters.revenue;
677                        } else {
678                            return revenueDelta >= filters.revenue;
679                        }
680                    });
681                }
682                
683                // Применяем фильтры по текущей цене
684                if (filters.currentPrice > 0) {
685                    filteredData = filteredData.filter(item => {
686                        let competitorPrice;
687                        if (method === 'max') competitorPrice = item.competitorMaxPrice;
688                        else if (method === 'avg') competitorPrice = item.competitorAvgPrice;
689                        else if (method === 'min') competitorPrice = item.competitorMinPrice;
690                        else if (method === 'median') competitorPrice = item.competitorMedianPrice;
691                        else competitorPrice = item.competitorWeightedPrice;
692                        
693                        const priceDelta = ((item.ourCurrentPrice - competitorPrice) / competitorPrice) * 100;
694                        if (filters.currentPriceDirection === 'less') {
695                            return priceDelta <= -filters.currentPrice;
696                        } else {
697                            return priceDelta >= filters.currentPrice;
698                        }
699                    });
700                }
701                
702                // Применяем фильтры по средней цене
703                if (filters.avgPrice > 0) {
704                    filteredData = filteredData.filter(item => {
705                        const avgPriceDelta = ((item.ourAvgPrice - item.competitorAvgPriceValue) / item.competitorAvgPriceValue) * 100;
706                        if (filters.avgPriceDirection === 'less') {
707                            return avgPriceDelta <= -filters.avgPrice;
708                        } else {
709                            return avgPriceDelta >= filters.avgPrice;
710                        }
711                    });
712                }
713                
714                // Применяем сортировку
715                if (sortColumn) {
716                    filteredData.sort((a, b) => {
717                        let aVal, bVal;
718                        
719                        switch(sortColumn) {
720                        case 'sku':
721                            aVal = a.sku;
722                            bVal = b.sku;
723                            break;
724                        case 'name':
725                            aVal = (a.name || '').toLowerCase();
726                            bVal = (b.name || '').toLowerCase();
727                            break;
728                        case 'query':
729                            aVal = a.query.toLowerCase();
730                            bVal = b.query.toLowerCase();
731                            break;
732                        case 'ourCurrentPrice':
733                            aVal = a.ourCurrentPrice;
734                            bVal = b.ourCurrentPrice;
735                            break;
736                        case 'competitorPrice':
737                            if (method === 'max') {
738                                aVal = a.competitorMaxPrice;
739                                bVal = b.competitorMaxPrice;
740                            } else if (method === 'avg') {
741                                aVal = a.competitorAvgPrice;
742                                bVal = b.competitorAvgPrice;
743                            } else {
744                                aVal = a.competitorWeightedPrice;
745                                bVal = b.competitorWeightedPrice;
746                            }
747                            break;
748                        case 'currentPriceDelta':
749                            let compPriceA = method === 'max' ? a.competitorMaxPrice : (method === 'avg' ? a.competitorAvgPrice : a.competitorWeightedPrice);
750                            let compPriceB = method === 'max' ? b.competitorMaxPrice : (method === 'avg' ? b.competitorAvgPrice : b.competitorWeightedPrice);
751                            aVal = ((a.ourCurrentPrice - compPriceA) / compPriceA) * 100;
752                            bVal = ((b.ourCurrentPrice - compPriceB) / compPriceB) * 100;
753                            break;
754                        case 'ourAvgPrice':
755                            aVal = a.ourAvgPrice;
756                            bVal = b.ourAvgPrice;
757                            break;
758                        case 'competitorAvgPrice':
759                            aVal = a.competitorAvgPriceValue;
760                            bVal = b.competitorAvgPriceValue;
761                            break;
762                        case 'avgPriceDelta':
763                            aVal = ((a.ourAvgPrice - a.competitorAvgPriceValue) / a.competitorAvgPriceValue) * 100;
764                            bVal = ((b.ourAvgPrice - b.competitorAvgPriceValue) / b.competitorAvgPriceValue) * 100;
765                            break;
766                        case 'ourRevenue':
767                            aVal = a.ourRevenue;
768                            bVal = b.ourRevenue;
769                            break;
770                        case 'competitorRevenue':
771                            aVal = a.competitorRevenue;
772                            bVal = b.competitorRevenue;
773                            break;
774                        case 'revenueDelta':
775                            aVal = ((a.ourRevenue - a.competitorRevenue) / a.competitorRevenue) * 100;
776                            bVal = ((b.ourRevenue - b.competitorRevenue) / b.competitorRevenue) * 100;
777                            break;
778                        default:
779                            return 0;
780                        }
781                        
782                        if (typeof aVal === 'string') {
783                            return sortDirection === 'asc' ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal);
784                        } else {
785                            return sortDirection === 'asc' ? aVal - bVal : bVal - aVal;
786                        }
787                    });
788                }
789                
790                let tableHTML = `
791                    <div class="ozon-parser-info">
792                        Найдено товаров: ${filteredData.length} из ${ourProductsData.length}
793                    </div>
794                    <table class="ozon-parser-results-table">
795                        <thead>
796                            <tr>
797                                <th rowspan="2" data-sort="sku" style="cursor: pointer; user-select: none;">SKU ${sortColumn === 'sku' ? (sortDirection === 'asc' ? '▲' : '▼') : ''}</th>
798                                <th rowspan="2" data-sort="name" style="cursor: pointer; user-select: none;">Название ${sortColumn === 'name' ? (sortDirection === 'asc' ? '▲' : '▼') : ''}</th>
799                                <th rowspan="2" data-sort="query" style="cursor: pointer; user-select: none;">Запрос ${sortColumn === 'query' ? (sortDirection === 'asc' ? '▲' : '▼') : ''}</th>
800                                <th colspan="3">Текущая цена</th>
801                                <th colspan="3">Средняя цена</th>
802                                <th colspan="3">Выручка</th>
803                            </tr>
804                            <tr>
805                                <th data-sort="ourCurrentPrice" style="cursor: pointer; user-select: none;">Наша ${sortColumn === 'ourCurrentPrice' ? (sortDirection === 'asc' ? '▲' : '▼') : ''}</th>
806                                <th data-sort="competitorPrice" style="cursor: pointer; user-select: none;">Конкуренты ${sortColumn === 'competitorPrice' ? (sortDirection === 'asc' ? '▲' : '▼') : ''}</th>
807                                <th data-sort="currentPriceDelta" style="cursor: pointer; user-select: none;">Дельта ${sortColumn === 'currentPriceDelta' ? (sortDirection === 'asc' ? '▲' : '▼') : ''}</th>
808                                <th data-sort="ourAvgPrice" style="cursor: pointer; user-select: none;">Наша ${sortColumn === 'ourAvgPrice' ? (sortDirection === 'asc' ? '▲' : '▼') : ''}</th>
809                                <th data-sort="competitorAvgPrice" style="cursor: pointer; user-select: none;">Конкуренты ${sortColumn === 'competitorAvgPrice' ? (sortDirection === 'asc' ? '▲' : '▼') : ''}</th>
810                                <th data-sort="avgPriceDelta" style="cursor: pointer; user-select: none;">Дельта ${sortColumn === 'avgPriceDelta' ? (sortDirection === 'asc' ? '▲' : '▼') : ''}</th>
811                                <th data-sort="ourRevenue" style="cursor: pointer; user-select: none;">Наша ${sortColumn === 'ourRevenue' ? (sortDirection === 'asc' ? '▲' : '▼') : ''}</th>
812                                <th data-sort="competitorRevenue" style="cursor: pointer; user-select: none;">Конкуренты ${sortColumn === 'competitorRevenue' ? (sortDirection === 'asc' ? '▲' : '▼') : ''}</th>
813                                <th data-sort="revenueDelta" style="cursor: pointer; user-select: none;">Дельта ${sortColumn === 'revenueDelta' ? (sortDirection === 'asc' ? '▲' : '▼') : ''}</th>
814                            </tr>
815                        </thead>
816                        <tbody>
817                `;
818                
819                filteredData.forEach(item => {
820                    // Выбираем цену конкурентов в зависимости от метода
821                    let competitorPrice;
822                    if (method === 'max') competitorPrice = item.competitorMaxPrice;
823                    else if (method === 'avg') competitorPrice = item.competitorAvgPrice;
824                    else if (method === 'min') competitorPrice = item.competitorMinPrice;
825                    else if (method === 'median') competitorPrice = item.competitorMedianPrice;
826                    else competitorPrice = item.competitorWeightedPrice;
827                    
828                    // Рассчитываем дельты
829                    const currentPriceDelta = ((item.ourCurrentPrice - competitorPrice) / competitorPrice) * 100;
830                    const avgPriceDelta = ((item.ourAvgPrice - item.competitorAvgPriceValue) / item.competitorAvgPriceValue) * 100;
831                    const revenueDelta = ((item.ourRevenue - item.competitorRevenue) / item.competitorRevenue) * 100;
832                    
833                    // Добавляем индикатор привязки к запросу
834                    const boundIndicator = item.isBound ? '<span style="color: #28a745; font-weight: 600; margin-left: 5px;" title="Запрос привязан к товару">🔒</span>' : '';
835                    
836                    tableHTML += `
837                        <tr>
838                            <td><a href="https://www.ozon.ru/product/${item.sku}" target="_blank" class="ozon-parser-sku-link">${item.sku}</a></td>
839                            <td style="max-width: 300px; white-space: normal;">${item.name || '—'}</td>
840                            <td><span class="ozon-parser-query-clickable" data-sku="${item.sku}" style="color: #005bff; cursor: pointer; text-decoration: underline;">${item.query}${boundIndicator}</span></td>
841                            
842                            <td style="font-weight: 600;">${Math.round(item.ourCurrentPrice).toLocaleString('ru-RU')} ₽</td>
843                            <td>${Math.round(competitorPrice).toLocaleString('ru-RU')} ₽</td>
844                            <td style="color: ${currentPriceDelta >= 0 ? '#dc3545' : '#28a745'}; font-weight: 600;">
845                                ${currentPriceDelta > 0 ? '+' : ''}${currentPriceDelta.toFixed(1)}%
846                            </td>
847                            
848                            <td style="font-weight: 600;">${Math.round(item.ourAvgPrice).toLocaleString('ru-RU')} ₽</td>
849                            <td>${Math.round(item.competitorAvgPriceValue).toLocaleString('ru-RU')} ₽</td>
850                            <td style="color: ${avgPriceDelta >= 0 ? '#dc3545' : '#28a745'}; font-weight: 600;">
851                                ${avgPriceDelta > 0 ? '+' : ''}${avgPriceDelta.toFixed(1)}%
852                            </td>
853                            
854                            <td style="font-weight: 600;">${Math.round(item.ourRevenue).toLocaleString('ru-RU')} ₽</td>
855                            <td>${Math.round(item.competitorRevenue).toLocaleString('ru-RU')} ₽</td>
856                            <td style="color: ${revenueDelta >= 0 ? '#28a745' : '#dc3545'}; font-weight: 600;">
857                                ${revenueDelta > 0 ? '+' : ''}${revenueDelta.toFixed(1)}%
858                            </td>
859                        </tr>
860                    `;
861                });
862                
863                tableHTML += `
864                        </tbody>
865                    </table>
866                `;
867                
868                const tableContainer = content.querySelector('#prices-table-container');
869                tableContainer.innerHTML = tableHTML;
870                
871                // Добавляем обработчики сортировки
872                tableContainer.querySelectorAll('th[data-sort]').forEach(th => {
873                    th.addEventListener('click', () => {
874                        const column = th.getAttribute('data-sort');
875                        if (sortColumn === column) {
876                            sortDirection = sortDirection === 'asc' ? 'desc' : 'asc';
877                        } else {
878                            sortColumn = column;
879                            sortDirection = 'asc';
880                        }
881                        displayPricesTable(currentMethod, currentFilters);
882                    });
883                });
884                
885                // Добавляем обработчики кликов на запросы для выбора привязки
886                tableContainer.querySelectorAll('.ozon-parser-query-clickable').forEach(querySpan => {
887                    querySpan.addEventListener('click', async (e) => {
888                        e.preventDefault();
889                        e.stopPropagation();
890                        const sku = querySpan.getAttribute('data-sku');
891                        await showQuerySelectionModal(sku, allListResults, skuQueryBindings);
892                    });
893                });
894                
895                // Добавляем обработчики кликов на строки для открытия панели с деталями
896                tableContainer.querySelectorAll('tbody tr').forEach(row => {
897                    row.classList.add('ozon-parser-clickable-row');
898                    row.addEventListener('click', async (e) => {
899                        // Не открываем панель, если кликнули на ссылку или запрос
900                        if (e.target.tagName === 'A' || e.target.classList.contains('ozon-parser-query-clickable')) return;
901                        
902                        const skuLink = row.querySelector('.ozon-parser-sku-link');
903                        if (!skuLink) return;
904                        
905                        const sku = skuLink.textContent.trim();
906                        const productData = filteredData.find(item => item.sku === sku);
907                        
908                        if (productData) {
909                            await showProductDetailsPanel(productData, allListResults);
910                        }
911                    });
912                });
913            }
914            
915            content.innerHTML = `
916                <div class="ozon-parser-modal-header">💰 Анализ цен</div>
917                <div class="ozon-parser-modal-body">
918                    <div class="ozon-parser-info">
919                        Сравнение цен наших товаров с топ-3 конкурентами по позиции в выдаче
920                    </div>
921                    
922                    <div style="margin-bottom: 20px; padding: 15px; background: #f8f9fa; border-radius: 8px;">
923                        <label style="font-size: 14px; font-weight: 600; margin-bottom: 8px; display: block;">Метод расчета цены конкурентов:</label>
924                        <select class="ozon-parser-search" id="price-method-selector" style="margin-bottom: 0;">
925                            <option value="max">Максимум по топ-3</option>
926                            <option value="avg">Среднее по топ-3</option>
927                            <option value="weighted">Средневзвешенное по выручке</option>
928                            <option value="min">Минимальная по топ-3</option>
929                            <option value="median">Медиана по топ-3</option>
930                        </select>
931                    </div>
932                    
933                    <div style="margin-bottom: 20px; padding: 15px; background: #f8f9fa; border-radius: 8px;">
934                        <label style="font-size: 14px; font-weight: 600; margin-bottom: 12px; display: block;">Фильтры (AND логика):</label>
935                        
936                        <div id="selected-query-info" style="display: none; padding: 10px; background: #e7f3ff; border-left: 4px solid #005bff; border-radius: 4px; margin-bottom: 15px;">
937                            <div style="display: flex; justify-content: space-between; align-items: center;">
938                                <span style="font-size: 13px; color: #333;">Фильтр по запросу: <strong id="selected-query-text"></strong></span>
939                                <button id="clear-query-filter" style="background: #dc3545; color: white; border: none; padding: 4px 12px; border-radius: 4px; cursor: pointer; font-size: 12px;">Очистить</button>
940                            </div>
941                        </div>
942                        
943                        <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px; margin-bottom: 15px;">
944                            <div>
945                                <label style="font-size: 12px; color: #666; margin-bottom: 5px; display: block;">Поиск по запросу</label>
946                                <input type="text" class="ozon-parser-search" id="filter-query-search" placeholder="Введите запрос" style="margin-bottom: 0;">
947                            </div>
948                            <div>
949                                <label style="font-size: 12px; color: #666; margin-bottom: 5px; display: block;">Поиск по SKU</label>
950                                <input type="text" class="ozon-parser-search" id="filter-sku-search" placeholder="Введите SKU" style="margin-bottom: 0;">
951                            </div>
952                        </div>
953                        
954                        <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px;">
955                            <div>
956                                <label style="font-size: 12px; color: #666; margin-bottom: 5px; display: block;">Выручка</label>
957                                <div style="display: flex; gap: 5px;">
958                                    <select class="ozon-parser-search" id="filter-revenue-direction" style="margin-bottom: 0; flex: 0 0 auto; width: 100px;">
959                                        <option value="less">Меньше</option>
960                                        <option value="more">Больше</option>
961                                    </select>
962                                    <input type="number" class="ozon-parser-search" id="filter-revenue" placeholder="0" min="0" step="1" value="0" style="margin-bottom: 0; flex: 1;">
963                                </div>
964                                <div style="font-size: 10px; color: #999; margin-top: 2px;">на % (и более)</div>
965                            </div>
966                            <div>
967                                <label style="font-size: 12px; color: #666; margin-bottom: 5px; display: block;">Текущая цена</label>
968                                <div style="display: flex; gap: 5px;">
969                                    <select class="ozon-parser-search" id="filter-current-price-direction" style="margin-bottom: 0; flex: 0 0 auto; width: 100px;">
970                                        <option value="more">Больше</option>
971                                        <option value="less">Меньше</option>
972                                    </select>
973                                    <input type="number" class="ozon-parser-search" id="filter-current-price" placeholder="0" min="0" step="1" value="0" style="margin-bottom: 0; flex: 1;">
974                                </div>
975                                <div style="font-size: 10px; color: #999; margin-top: 2px;">на % (и более)</div>
976                            </div>
977                            <div>
978                                <label style="font-size: 12px; color: #666; margin-bottom: 5px; display: block;">Средняя цена</label>
979                                <div style="display: flex; gap: 5px;">
980                                    <select class="ozon-parser-search" id="filter-avg-price-direction" style="margin-bottom: 0; flex: 0 0 auto; width: 100px;">
981                                        <option value="more">Больше</option>
982                                        <option value="less">Меньше</option>
983                                    </select>
984                                    <input type="number" class="ozon-parser-search" id="filter-avg-price" placeholder="0" min="0" step="1" value="0" style="margin-bottom: 0; flex: 1;">
985                                </div>
986                                <div style="font-size: 10px; color: #999; margin-top: 2px;">на % (и более)</div>
987                            </div>
988                        </div>
989                    </div>
990                    
991                    <div id="prices-table-container"></div>
992                </div>
993                <div class="ozon-parser-modal-footer">
994                    <button class="ozon-parser-btn secondary" id="export-csv-btn">Экспорт в CSV</button>
995					<button class="ozon-parser-btn" id="send-to-dashboard-btn" style="background: linear-gradient(135deg, #6c5ce7 0%, #a29bfe 100%); margin-right: 10px;">📡 Отправить в дашборд</button>
996                    <button class="ozon-parser-btn" id="close-prices-modal">Закрыть</button>
997                </div>
998            `;
999            
1000            modal.appendChild(content);
1001            document.body.appendChild(modal);
1002            
1003            // Начальное отображение
1004            let currentMethod = 'max';
1005            let currentFilters = { 
1006                revenue: 0, 
1007                revenueDirection: 'less',
1008                currentPrice: 0, 
1009                currentPriceDirection: 'more',
1010                avgPrice: 0,
1011                avgPriceDirection: 'more',
1012                querySearch: '',
1013                skuSearch: '',
1014                selectedQuery: ''
1015            };
1016            
1017            // Загружаем сохраненный запрос
1018            const savedQuery = await GM.getValue('ozon_parser_selected_query', '');
1019            if (savedQuery) {
1020                currentFilters.selectedQuery = savedQuery;
1021            }
1022            
1023            displayPricesTable(currentMethod, currentFilters);
1024            
1025            // Обновляем информационную панель после первого рендера
1026            if (savedQuery) {
1027                const selectedQueryInfo = content.querySelector('#selected-query-info');
1028                const selectedQueryText = content.querySelector('#selected-query-text');
1029                if (selectedQueryInfo && selectedQueryText) {
1030                    selectedQueryInfo.style.display = 'block';
1031                    selectedQueryText.textContent = savedQuery;
1032                }
1033            }
1034            
1035            // Функция для обновления фильтров из полей ввода
1036            function updateFiltersFromInputs() {
1037                currentFilters = {
1038                    revenue: parseFloat(content.querySelector('#filter-revenue').value) || 0,
1039                    revenueDirection: content.querySelector('#filter-revenue-direction').value,
1040                    currentPrice: parseFloat(content.querySelector('#filter-current-price').value) || 0,
1041                    currentPriceDirection: content.querySelector('#filter-current-price-direction').value,
1042                    avgPrice: parseFloat(content.querySelector('#filter-avg-price').value) || 0,
1043                    avgPriceDirection: content.querySelector('#filter-avg-price-direction').value,
1044                    querySearch: content.querySelector('#filter-query-search').value.trim(),
1045                    skuSearch: content.querySelector('#filter-sku-search').value.trim(),
1046                    selectedQuery: currentFilters.selectedQuery
1047                };
1048                displayPricesTable(currentMethod, currentFilters);
1049            }
1050            
1051            // Обработчик очистки фильтра по запросу
1052            const clearQueryFilterBtn = content.querySelector('#clear-query-filter');
1053            if (clearQueryFilterBtn) {
1054                clearQueryFilterBtn.addEventListener('click', async () => {
1055                    currentFilters.selectedQuery = '';
1056                    await GM.setValue('ozon_parser_selected_query', '');
1057                    const selectedQueryInfo = content.querySelector('#selected-query-info');
1058                    if (selectedQueryInfo) {
1059                        selectedQueryInfo.style.display = 'none';
1060                    }
1061                    displayPricesTable(currentMethod, currentFilters);
1062                });
1063            }
1064            
1065            // Обработчик изменения метода расчета
1066            const methodSelector = content.querySelector('#price-method-selector');
1067            methodSelector.addEventListener('change', () => {
1068                currentMethod = methodSelector.value;
1069                displayPricesTable(currentMethod, currentFilters);
1070            });
1071            
1072            // Обработчики для всех фильтров - применяем автоматически
1073            content.querySelector('#filter-query-search').addEventListener('input', debounce(updateFiltersFromInputs, 300));
1074            content.querySelector('#filter-sku-search').addEventListener('input', debounce(updateFiltersFromInputs, 300));
1075            
1076            content.querySelector('#filter-revenue').addEventListener('input', debounce(updateFiltersFromInputs, 500));
1077            content.querySelector('#filter-revenue-direction').addEventListener('change', updateFiltersFromInputs);
1078            
1079            content.querySelector('#filter-current-price').addEventListener('input', debounce(updateFiltersFromInputs, 500));
1080            content.querySelector('#filter-current-price-direction').addEventListener('change', updateFiltersFromInputs);
1081            
1082            content.querySelector('#filter-avg-price').addEventListener('input', debounce(updateFiltersFromInputs, 500));
1083            content.querySelector('#filter-avg-price-direction').addEventListener('change', updateFiltersFromInputs);
1084            
1085            // Обработчик экспорта в CSV
1086            content.querySelector('#export-csv-btn').addEventListener('click', () => {
1087                exportToCSV(ourProductsData, currentMethod, currentFilters);
1088            });
1089			// Обработчик отправки в дашборд
1090content.querySelector('#send-to-dashboard-btn').addEventListener('click', async () => {
1091    const btn = content.querySelector('#send-to-dashboard-btn');
1092    btn.disabled = true;
1093    btn.textContent = '⏳ Отправка...';
1094    
1095    try {
1096        // Формируем данные для отправки
1097      const dataToSend = ourProductsData.map(item => {
1098    // Получаем текущий метод из селектора
1099    const methodSelector = content.querySelector('#price-method-selector');
1100    const selectedMethod = methodSelector ? methodSelector.value : 'max';
1101    
1102    // Выбираем цену конкурента по методу
1103    let competitorPriceCurrent;
1104    switch(selectedMethod) {
1105        case 'max': competitorPriceCurrent = item.competitorMaxPrice; break;
1106        case 'avg': competitorPriceCurrent = item.competitorAvgPrice; break;
1107        case 'weighted': competitorPriceCurrent = item.competitorWeightedPrice; break;
1108        case 'min': competitorPriceCurrent = item.competitorMinPrice; break;
1109        case 'median': competitorPriceCurrent = item.competitorMedianPrice; break;
1110        default: competitorPriceCurrent = item.competitorMaxPrice;
1111    }
1112    
1113    return {
1114        sku: String(item.sku),
1115        title: String(item.name || 'Без названия'),
1116        query: String(item.query || 'неизвестно'),
1117        list: String(item.listName || 'Без названия'),
1118        our_price_current: Math.round(item.ourCurrentPrice || 0),
1119        competitor_price_current: Math.round(competitorPriceCurrent || 0),
1120        delta_current_percent: parseFloat((((item.ourCurrentPrice - competitorPriceCurrent) / Math.max(competitorPriceCurrent || 1, 1)) * 100).toFixed(1)),
1121        our_price_avg: Math.round(item.ourAvgPrice || 0),
1122        competitor_price_avg: Math.round(item.competitorAvgPriceValue || item.competitorAvgPrice || 0),
1123        delta_avg_percent: parseFloat((((item.ourAvgPrice - (item.competitorAvgPriceValue || item.competitorAvgPrice || 0)) / Math.max(item.competitorAvgPriceValue || item.competitorAvgPrice || 1, 1)) * 100).toFixed(1)),
1124        our_revenue: Math.round(item.ourRevenue || 0),
1125        competitor_revenue: Math.round(item.competitorRevenue || 0),
1126        delta_revenue_percent: parseFloat((((item.ourRevenue - item.competitorRevenue) / Math.max(item.competitorRevenue || 1, 1)) * 100).toFixed(1)),
1127        parsed_at: new Date().toISOString(),
1128        method_used: selectedMethod // для отладки
1129    };
1130});
1131        
1132        // Отправляем на Blink
1133        const response = await fetch('https://8p6h747w--products.functions.blink.new/products', {
1134            method: 'POST',
1135            headers: {
1136                'Content-Type': 'application/json',
1137                'X-API-Key': 'secret123'
1138            },
1139            body: JSON.stringify(dataToSend)
1140        });
1141        
1142        if (!response.ok) {
1143            throw new Error(`HTTP ${response.status}: ${response.statusText}`);
1144        }
1145        
1146        const result = await response.json();
1147        btn.textContent = '✅ Отправлено!';
1148        btn.style.background = 'linear-gradient(135deg, #00b894 0%, #00cec9 100%)';
1149        
1150        alert(`Успешно отправлено ${dataToSend.length} товаров в дашборд!\n\nОтвет сервера: ${JSON.stringify(result, null, 2)}`);
1151        
1152        setTimeout(() => {
1153            btn.disabled = false;
1154            btn.textContent = '📡 Отправить в дашборд';
1155            btn.style.background = 'linear-gradient(135deg, #6c5ce7 0%, #a29bfe 100%)';
1156        }, 3000);
1157        
1158    } catch (error) {
1159        console.error('Ozon Product Parser: Error sending to dashboard:', error);
1160        btn.textContent = '❌ Ошибка';
1161        btn.style.background = 'linear-gradient(135deg, #e74c3c 0%, #c0392b 100%)';
1162        
1163        alert('Ошибка при отправке в дашборд:\n\n' + error.message + '\n\nПроверьте:\n1. URL дашборда (возможно он изменился)\n2. Подключение к интернету\n3. CORS политику (для разработки)');
1164        
1165        setTimeout(() => {
1166            btn.disabled = false;
1167            btn.textContent = '📡 Отправить в дашборд';
1168            btn.style.background = 'linear-gradient(135deg, #6c5ce7 0%, #a29bfe 100%)';
1169        }, 3000);
1170    }
1171});
1172            
1173            // Обработчик закрытия
1174            content.querySelector('#close-prices-modal').addEventListener('click', () => {
1175                modal.remove();
1176            });
1177            
1178            modal.addEventListener('click', (e) => {
1179                if (e.target === modal) {
1180                    modal.remove();
1181                }
1182            });
1183            
1184            console.log('Ozon Product Parser: Prices modal shown');
1185        } catch (error) {
1186            console.error('Ozon Product Parser: Error in showPricesModal:', error);
1187            alert('Ошибка при отображении цен: ' + error.message);
1188        }
1189    }
1190
1191    // Показываем модальное окно выбора запроса для товара
1192    async function showQuerySelectionModal(sku, allListResults, skuQueryBindings) {
1193        console.log('Ozon Product Parser: Opening query selection modal for SKU:', sku);
1194        
1195        // Собираем все запросы, где встречается этот товар
1196        const queriesWithProduct = [];
1197        for (const listName in allListResults) {
1198            const listData = allListResults[listName];
1199            for (const query in listData.queries) {
1200                const products = listData.queries[query];
1201                const product = products.find(p => p.sku === sku);
1202                if (product) {
1203                    // Находим конкурентов для этого запроса
1204                    const competitors = products.filter(p => !p.isTargetBrand && p.price > 0);
1205                    const sortedCompetitors = [...competitors].sort((a, b) => a.position - b.position);
1206                    const top3Competitors = sortedCompetitors.slice(0, 3);
1207                    
1208                    if (top3Competitors.length > 0) {
1209                        const competitorPrices = top3Competitors.map(c => c.price);
1210                        const competitorRevenues = top3Competitors.map(c => c.revenue);
1211                        
1212                        const competitorMaxPrice = Math.max(...competitorPrices);
1213                        const competitorAvgPrice = competitorPrices.reduce((sum, p) => sum + p, 0) / competitorPrices.length;
1214                        const competitorRevenue = competitorRevenues.reduce((sum, r) => sum + r, 0) / competitorRevenues.length;
1215                        
1216                        queriesWithProduct.push({
1217                            listName: listName,
1218                            query: query,
1219                            product: product,
1220                            competitorMaxPrice: competitorMaxPrice,
1221                            competitorAvgPrice: competitorAvgPrice,
1222                            competitorRevenue: competitorRevenue
1223                        });
1224                    }
1225                }
1226            }
1227        }
1228        
1229        if (queriesWithProduct.length === 0) {
1230            alert('Товар не найден ни в одном запросе');
1231            return;
1232        }
1233        
1234        const modal = document.createElement('div');
1235        modal.className = 'ozon-parser-modal';
1236        modal.style.zIndex = '10003';
1237        
1238        const content = document.createElement('div');
1239        content.className = 'ozon-parser-modal-content';
1240        content.style.maxWidth = '900px';
1241        
1242        const currentBoundQuery = skuQueryBindings[sku];
1243        
1244        let modalHTML = `
1245            <div class="ozon-parser-modal-header">Выбор запроса для SKU ${sku}</div>
1246            <div class="ozon-parser-modal-body">
1247                <div class="ozon-parser-info">
1248                    Нажмите на значок замка, чтобы закрепить запрос для этого товара.
1249                    ${currentBoundQuery ? `<br><strong>Текущая привязка:</strong> ${currentBoundQuery}` : ''}
1250                </div>
1251                <table class="ozon-parser-results-table">
1252                    <thead>
1253                        <tr>
1254                            <th style="width: 40px;"></th>
1255                            <th>Запрос</th>
1256                            <th>Список</th>
1257                            <th>Позиция</th>
1258                            <th>Выручка товара</th>
1259                            <th>Макс. цена конкурентов</th>
1260                            <th>Средняя цена конкурентов</th>
1261                            <th>Средняя выручка конкурентов</th>
1262                        </tr>
1263                    </thead>
1264                    <tbody>
1265        `;
1266        
1267        queriesWithProduct.forEach(item => {
1268            const isBound = currentBoundQuery === item.query;
1269            const rowStyle = isBound ? 'background: #d4edda;' : '';
1270            const lockIcon = isBound ? '🔒' : '🔓';
1271            const lockColor = isBound ? '#28a745' : '#999';
1272            
1273            modalHTML += `
1274                <tr style="${rowStyle}">
1275                    <td style="text-align: center;">
1276                        <span class="query-lock-icon" data-query="${item.query}" style="cursor: pointer; font-size: 20px; color: ${lockColor}; user-select: none;" title="${isBound ? 'Отвязать запрос' : 'Закрепить запрос'}">${lockIcon}</span>
1277                    </td>
1278                    <td style="font-weight: ${isBound ? '600' : 'normal'};">${item.query}</td>
1279                    <td>${item.listName}</td>
1280                    <td>${item.product.position}</td>
1281                    <td>${Math.round(item.product.revenue).toLocaleString('ru-RU')} ₽</td>
1282                    <td>${Math.round(item.competitorMaxPrice).toLocaleString('ru-RU')} ₽</td>
1283                    <td>${Math.round(item.competitorAvgPrice).toLocaleString('ru-RU')} ₽</td>
1284                    <td>${Math.round(item.competitorRevenue).toLocaleString('ru-RU')} ₽</td>
1285                </tr>
1286            `;
1287        });
1288        
1289        modalHTML += `
1290                    </tbody>
1291                </table>
1292            </div>
1293            <div class="ozon-parser-modal-footer">
1294                <button class="ozon-parser-btn" id="close-query-selection-modal">Закрыть</button>
1295            </div>
1296        `;
1297        
1298        content.innerHTML = modalHTML;
1299        modal.appendChild(content);
1300        document.body.appendChild(modal);
1301        
1302        // Обработчики кликов на замки
1303        content.querySelectorAll('.query-lock-icon').forEach(lockIcon => {
1304            lockIcon.addEventListener('click', async () => {
1305                const selectedQuery = lockIcon.getAttribute('data-query');
1306                const isBound = currentBoundQuery === selectedQuery;
1307                
1308                if (isBound) {
1309                    // Отвязываем запрос
1310                    if (confirm(`Отвязать запрос "${selectedQuery}" от товара ${sku}?`)) {
1311                        delete skuQueryBindings[sku];
1312                        await GM.setValue('ozon_parser_sku_query_bindings', JSON.stringify(skuQueryBindings));
1313                        
1314                        console.log(`Ozon Product Parser: Unbound SKU ${sku} from query "${selectedQuery}"`);
1315                        
1316                        modal.remove();
1317                        
1318                        // Перезагружаем таблицу цен
1319                        await showPricesModal();
1320                    }
1321                } else {
1322                    // Привязываем запрос
1323                    skuQueryBindings[sku] = selectedQuery;
1324                    await GM.setValue('ozon_parser_sku_query_bindings', JSON.stringify(skuQueryBindings));
1325                    
1326                    console.log(`Ozon Product Parser: Bound SKU ${sku} to query "${selectedQuery}"`);
1327                    
1328                    modal.remove();
1329                    
1330                    // Перезагружаем таблицу цен
1331                    await showPricesModal();
1332                }
1333            });
1334        });
1335        
1336        // Закрытие
1337        content.querySelector('#close-query-selection-modal').addEventListener('click', () => {
1338            modal.remove();
1339        });
1340        
1341        modal.addEventListener('click', (e) => {
1342            if (e.target === modal) {
1343                modal.remove();
1344            }
1345        });
1346    }
1347    
1348    // Показываем панель с деталями товара
1349    async function showProductDetailsPanel(productData, allListResults) {
1350        console.log('Ozon Product Parser: Opening product details panel for SKU:', productData.sku);
1351        
1352        // Удаляем существующую панель, если она есть
1353        const existingPanel = document.querySelector('.ozon-parser-details-panel');
1354        if (existingPanel) {
1355            existingPanel.remove();
1356        }
1357        
1358        // Создаем панель
1359        const panel = document.createElement('div');
1360        panel.className = 'ozon-parser-details-panel';
1361        
1362        // Загружаем привязки SKU к запросам
1363        const skuQueryBindingsJson = await GM.getValue('ozon_parser_sku_query_bindings', '{}');
1364        const skuQueryBindings = JSON.parse(skuQueryBindingsJson);
1365        const boundQuery = skuQueryBindings[productData.sku];
1366        
1367        // Собираем все запросы, где встречается этот товар
1368        const queriesWithProduct = [];
1369        for (const listName in allListResults) {
1370            const listData = allListResults[listName];
1371            for (const query in listData.queries) {
1372                const products = listData.queries[query];
1373                const product = products.find(p => p.sku === productData.sku);
1374                if (product) {
1375                    // Если есть привязка и это не тот запрос - пропускаем
1376                    if (boundQuery && boundQuery !== query) {
1377                        continue;
1378                    }
1379                    
1380                    queriesWithProduct.push({
1381                        listName: listName,
1382                        query: query,
1383                        product: product,
1384                        allProducts: products
1385                    });
1386                }
1387            }
1388        }
1389        
1390        // Функция для генерации HTML панели с аналитикой
1391        async function generatePanelHTML(queryData) {
1392            const analytics = await analyzeProducts(queryData.allProducts);
1393            
1394            return `
1395                <div class="ozon-parser-details-panel-header">
1396                    <div class="ozon-parser-details-panel-title">SKU: ${productData.sku}</div>
1397                    <button class="ozon-parser-details-panel-close">Закрыть</button>
1398                </div>
1399                <div class="ozon-parser-details-panel-body">
1400                    <div style="margin-bottom: 20px;">
1401                        <div style="font-size: 16px; font-weight: 600; margin-bottom: 10px;">${productData.name || 'Название не найдено'}</div>
1402                        <div style="font-size: 14px; color: #666; margin-bottom: 5px;">Текущая цена: <strong>${Math.round(productData.ourCurrentPrice).toLocaleString('ru-RU')} ₽</strong></div>
1403                        <div style="font-size: 14px; color: #666; margin-bottom: 5px;">Средняя цена: <strong>${Math.round(productData.ourAvgPrice).toLocaleString('ru-RU')} ₽</strong></div>
1404                        <div style="font-size: 14px; color: #666;">Выручка: <strong>${Math.round(productData.ourRevenue).toLocaleString('ru-RU')} ₽</strong></div>
1405                    </div>
1406                    
1407                    ${analytics && analytics.elasticity ? `
1408                    <div style="margin-bottom: 20px; padding: 15px; background: #e7f3ff; border-radius: 8px; border-left: 4px solid #005bff;">
1409                        <div style="font-size: 15px; font-weight: 600; margin-bottom: 10px; color: #005bff;">📊 Эластичность запроса</div>
1410                        <div style="font-size: 24px; font-weight: 700; color: #333; margin-bottom: 8px;">${analytics.elasticity.value}</div>
1411                        <div style="font-size: 13px; color: #666; line-height: 1.5;">
1412                            ${analytics.elasticity.interpretation}
1413                        </div>
1414                    </div>
1415                    ` : ''}
1416                    
1417                    ${analytics && analytics.recommendedPrices ? `
1418                    <div style="margin-bottom: 20px;">
1419                        <div style="font-size: 15px; font-weight: 600; margin-bottom: 12px; color: #005bff;">💰 Стратегии ценообразования</div>
1420                        
1421                        <div style="background: #f8f9fa; padding: 12px; border-radius: 8px; margin-bottom: 10px; border-left: 4px solid #6c757d;">
1422                            <div style="font-size: 14px; font-weight: 600; color: #6c757d; margin-bottom: 5px;">⚡ Захват рынка</div>
1423                            <div style="font-size: 20px; font-weight: 700; color: #333; margin-bottom: 5px;">${analytics.recommendedPrices.marketCapture.price.toLocaleString('ru-RU')}</div>
1424                            <div style="font-size: 12px; color: #666; margin-bottom: 8px;">${analytics.recommendedPrices.marketCapture.description}</div>
1425                            ${analytics.brandRecommendations && (analytics.brandRecommendations.gls || analytics.brandRecommendations.skinphoria) ? `
1426                            <div style="font-size: 11px; color: #666; margin-top: 8px; padding-top: 8px; border-top: 1px solid #e0e0e0;">
1427                                ${(() => {
1428        const ourProduct = (analytics.brandRecommendations.gls?.currentProducts || [])
1429            .concat(analytics.brandRecommendations.skinphoria?.currentProducts || [])
1430            .find(p => p.sku === productData.sku);
1431        if (!ourProduct || !ourProduct.recommendations || !ourProduct.recommendations.forecast || !ourProduct.recommendations.forecast.marketCapture) return '';
1432        const forecast = ourProduct.recommendations.forecast.marketCapture;
1433        return `
1434                                        <div style="color: ${forecast.revenueChange >= 0 ? '#28a745' : '#dc3545'};">Выручка: ${forecast.revenueChange > 0 ? '+' : ''}${forecast.revenueChange}%</div>
1435                                        <div style="color: ${forecast.ordersChange >= 0 ? '#28a745' : '#dc3545'};">Заказы: ${forecast.ordersChange > 0 ? '+' : ''}${forecast.ordersChange}%</div>
1436                                        ${forecast.profit !== null ? `<div style="color: ${forecast.profitChange >= 0 ? '#28a745' : '#dc3545'}; font-weight: 600;">Прибыль: ${forecast.profitChange > 0 ? '+' : ''}${forecast.profitChange}% (${forecast.profit.toFixed(0)} ₽)</div>` : ''}
1437                                    `;
1438    })()}
1439                            </div>
1440                            ` : ''}
1441                        </div>
1442                        
1443                        <div style="background: #fff3cd; padding: 12px; border-radius: 8px; margin-bottom: 10px; border-left: 4px solid #dc3545;">
1444                            <div style="font-size: 14px; font-weight: 600; color: #dc3545; margin-bottom: 5px;">✅ Оптимальная</div>
1445                            <div style="font-size: 20px; font-weight: 700; color: #333; margin-bottom: 5px;">${analytics.recommendedPrices.aggressive.price.toLocaleString('ru-RU')}</div>
1446                            <div style="font-size: 12px; color: #666; margin-bottom: 8px;">${analytics.recommendedPrices.aggressive.description}</div>
1447                            ${analytics.brandRecommendations && (analytics.brandRecommendations.gls || analytics.brandRecommendations.skinphoria) ? `
1448                            <div style="font-size: 11px; color: #666; margin-top: 8px; padding-top: 8px; border-top: 1px solid #e0e0e0;">
1449                                ${(() => {
1450        const ourProduct = (analytics.brandRecommendations.gls?.currentProducts || [])
1451            .concat(analytics.brandRecommendations.skinphoria?.currentProducts || [])
1452            .find(p => p.sku === productData.sku);
1453        if (!ourProduct || !ourProduct.recommendations || !ourProduct.recommendations.forecast || !ourProduct.recommendations.forecast.aggressive) return '';
1454        const forecast = ourProduct.recommendations.forecast.aggressive;
1455        return `
1456                                        <div style="color: ${forecast.revenueChange >= 0 ? '#28a745' : '#dc3545'};">Выручка: ${forecast.revenueChange > 0 ? '+' : ''}${forecast.revenueChange}%</div>
1457                                        <div style="color: ${forecast.ordersChange >= 0 ? '#28a745' : '#dc3545'};">Заказы: ${forecast.ordersChange > 0 ? '+' : ''}${forecast.ordersChange}%</div>
1458                                        ${forecast.profit !== null ? `<div style="color: ${forecast.profitChange >= 0 ? '#28a745' : '#dc3545'}; font-weight: 600;">Прибыль: ${forecast.profitChange > 0 ? '+' : ''}${forecast.profitChange}% (${forecast.profit.toFixed(0)} ₽)</div>` : ''}
1459                                    `;
1460    })()}
1461                            </div>
1462                            ` : ''}
1463                        </div>
1464                        
1465                        <div style="background: #d4edda; padding: 12px; border-radius: 8px; border-left: 4px solid #28a745;">
1466                            <div style="font-size: 14px; font-weight: 600; color: #28a745; margin-bottom: 5px;">🔥 Агрессивная</div>
1467                            <div style="font-size: 20px; font-weight: 700; color: #333; margin-bottom: 5px;">${analytics.recommendedPrices.optimal.price.toLocaleString('ru-RU')}</div>
1468                            <div style="font-size: 12px; color: #666; margin-bottom: 8px;">${analytics.recommendedPrices.optimal.description}</div>
1469                            ${analytics.brandRecommendations && (analytics.brandRecommendations.gls || analytics.brandRecommendations.skinphoria) ? `
1470                            <div style="font-size: 11px; color: #666; margin-top: 8px; padding-top: 8px; border-top: 1px solid #e0e0e0;">
1471                                ${(() => {
1472        const ourProduct = (analytics.brandRecommendations.gls?.currentProducts || [])
1473            .concat(analytics.brandRecommendations.skinphoria?.currentProducts || [])
1474            .find(p => p.sku === productData.sku);
1475        if (!ourProduct || !ourProduct.recommendations || !ourProduct.recommendations.forecast || !ourProduct.recommendations.forecast.optimal) return '';
1476        const forecast = ourProduct.recommendations.forecast.optimal;
1477        return `
1478                                        <div style="color: ${forecast.revenueChange >= 0 ? '#28a745' : '#dc3545'};">Выручка: ${forecast.revenueChange > 0 ? '+' : ''}${forecast.revenueChange}%</div>
1479                                        <div style="color: ${forecast.ordersChange >= 0 ? '#28a745' : '#dc3545'};">Заказы: ${forecast.ordersChange > 0 ? '+' : ''}${forecast.ordersChange}%</div>
1480                                        ${forecast.profit !== null ? `<div style="color: ${forecast.profitChange >= 0 ? '#28a745' : '#dc3545'}; font-weight: 600;">Прибыль: ${forecast.profitChange > 0 ? '+' : ''}${forecast.profitChange}% (${forecast.profit.toFixed(0)} ₽)</div>` : ''}
1481                                    `;
1482    })()}
1483                            </div>
1484                            ` : ''}
1485                        </div>
1486                    </div>
1487                    ` : ''}
1488                    
1489                    <div style="margin-bottom: 20px;">
1490                        <div style="font-size: 15px; font-weight: 600; margin-bottom: 10px; color: #005bff;">Запросы с этим товаром (${queriesWithProduct.length})</div>
1491                        <select class="ozon-parser-search" id="query-selector-panel" style="margin-bottom: 15px;">
1492                            ${queriesWithProduct.map((item, index) => `
1493                                <option value="${index}">${item.query} (${item.listName})</option>
1494                            `).join('')}
1495                        </select>
1496                        <div id="query-details-container"></div>
1497                    </div>
1498                </div>
1499            `;
1500        }
1501        
1502        // Получаем аналитику для первого запроса и формируем HTML
1503        const firstQueryData = queriesWithProduct[0];
1504        const initialHTML = await generatePanelHTML(firstQueryData);
1505        
1506        panel.innerHTML = initialHTML;
1507        document.body.appendChild(panel);
1508        
1509        // Функция для отображения деталей выбранного запроса
1510        function displayQueryDetails(queryIndex) {
1511            const queryData = queriesWithProduct[queryIndex];
1512            const product = queryData.product;
1513            const allProducts = queryData.allProducts;
1514            const query = queryData.query;
1515            
1516            // Находим топ-3 конкурентов по выручке
1517            const competitors = allProducts.filter(p => !p.isTargetBrand && p.price > 0 && p.revenue > 0);
1518            const sortedCompetitors = [...competitors].sort((a, b) => b.revenue - a.revenue);
1519            const top3Competitors = sortedCompetitors.slice(0, 3);
1520            
1521            // Позиция товара по выручке
1522            const sortedByRevenue = [...allProducts].sort((a, b) => b.revenue - a.revenue);
1523            const revenuePosition = sortedByRevenue.findIndex(p => p.sku === product.sku) + 1;
1524            
1525            // URL для поиска на Ozon
1526            const searchUrl = `https://www.ozon.ru/search/?text=${encodeURIComponent(query)}&from_global=true`;
1527            
1528            let detailsHTML = `
1529                <div style="padding: 15px; background: #f8f9fa; border-radius: 8px; margin-bottom: 15px;">
1530                    <div style="font-size: 14px; font-weight: 600; margin-bottom: 10px;">Позиции в выдаче</div>
1531                    <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px;">
1532                        <div>
1533                            <div style="font-size: 12px; color: #666;">По позиции</div>
1534                            <div style="font-size: 18px; font-weight: 700; color: #005bff;">${product.position} место</div>
1535                        </div>
1536                        <div>
1537                            <div style="font-size: 12px; color: #666;">По выручке</div>
1538                            <div style="font-size: 18px; font-weight: 700; color: #005bff;">${revenuePosition} место</div>
1539                        </div>
1540                    </div>
1541                    <div style="margin-top: 10px;">
1542                        <a href="${searchUrl}" target="_blank" class="ozon-parser-sku-link" style="font-size: 13px;">🔍 Открыть запрос на Ozon</a>
1543                    </div>
1544                </div>
1545                
1546                <div style="padding: 15px; background: #f8f9fa; border-radius: 8px; margin-bottom: 15px;">
1547                    <div style="font-size: 14px; font-weight: 600; margin-bottom: 10px;">Метрики товара</div>
1548                    <div style="font-size: 13px; color: #666; margin-bottom: 5px;">Цена: <strong>${product.price.toLocaleString('ru-RU')} ₽</strong></div>
1549                    <div style="font-size: 13px; color: #666; margin-bottom: 5px;">Выручка: <strong>${product.revenue.toLocaleString('ru-RU')} ₽</strong></div>
1550                    <div style="font-size: 13px; color: #666;">Заказы: <strong>${product.orders.toLocaleString('ru-RU')}</strong></div>
1551                </div>
1552            `;
1553            
1554            if (top3Competitors.length > 0) {
1555                detailsHTML += `
1556                    <div style="padding: 15px; background: #fff3cd; border-radius: 8px;">
1557                        <div style="font-size: 14px; font-weight: 600; margin-bottom: 10px;">Топ-3 конкурента по выручке</div>
1558                        <table class="ozon-parser-results-table" style="margin-top: 0;">
1559                            <thead>
1560                                <tr>
1561                                    <th>Позиция</th>
1562                                    <th>Бренд</th>
1563                                    <th>Текущая цена</th>
1564                                    <th>Средняя цена</th>
1565                                    <th>Выручка</th>
1566                                    <th>Заказы</th>
1567                                </tr>
1568                            </thead>
1569                            <tbody>
1570                `;
1571                
1572                top3Competitors.forEach(comp => {
1573                    const avgPrice = comp.orders > 0 ? comp.revenue / comp.orders : 0;
1574                    detailsHTML += `
1575                        <tr>
1576                            <td>${comp.position}</td>
1577                            <td><a href="https://www.ozon.ru/product/${comp.sku}" target="_blank" class="ozon-parser-sku-link">${comp.brand || '—'}</a></td>
1578                            <td>${comp.price.toLocaleString('ru-RU')} ₽</td>
1579                            <td>${Math.round(avgPrice).toLocaleString('ru-RU')} ₽</td>
1580                            <td>${comp.revenue.toLocaleString('ru-RU')} ₽</td>
1581                            <td>${comp.orders.toLocaleString('ru-RU')}</td>
1582                        </tr>
1583                    `;
1584                });
1585                
1586                detailsHTML += `
1587                            </tbody>
1588                        </table>
1589                    </div>
1590                `;
1591            }
1592            
1593            const container = panel.querySelector('#query-details-container');
1594            container.innerHTML = detailsHTML;
1595        }
1596        
1597        // Функция для обновления панели при смене запроса
1598        async function updatePanelForQuery(queryIndex) {
1599            const selectedQueryData = queriesWithProduct[queryIndex];
1600            
1601            // Пересчитываем аналитику для выбранного запроса
1602            const newHTML = await generatePanelHTML(selectedQueryData);
1603            panel.innerHTML = newHTML;
1604            
1605            // Переподключаем обработчики после обновления HTML
1606            const newQuerySelector = panel.querySelector('#query-selector-panel');
1607            newQuerySelector.value = queryIndex;
1608            
1609            // Переподключаем обработчик изменения запроса
1610            newQuerySelector.addEventListener('change', async () => {
1611                const newSelectedIndex = parseInt(newQuerySelector.value);
1612                await updatePanelForQuery(newSelectedIndex);
1613            });
1614            
1615            // Переподключаем обработчик закрытия
1616            const newCloseBtn = panel.querySelector('.ozon-parser-details-panel-close');
1617            newCloseBtn.addEventListener('click', () => {
1618                panel.classList.remove('open');
1619                setTimeout(() => panel.remove(), 300);
1620            });
1621            
1622            // Отображаем детали выбранного запроса
1623            displayQueryDetails(queryIndex);
1624        }
1625        
1626        // Отображаем детали первого запроса
1627        displayQueryDetails(0);
1628        
1629        // Обработчик изменения запроса
1630        const querySelector = panel.querySelector('#query-selector-panel');
1631        querySelector.addEventListener('change', async () => {
1632            const selectedIndex = parseInt(querySelector.value);
1633            await updatePanelForQuery(selectedIndex);
1634        });
1635        
1636        // Обработчик закрытия панели
1637        const closeBtn = panel.querySelector('.ozon-parser-details-panel-close');
1638        closeBtn.addEventListener('click', () => {
1639            panel.classList.remove('open');
1640            setTimeout(() => panel.remove(), 300);
1641        });
1642        
1643        // Открываем панель с анимацией
1644        setTimeout(() => {
1645            panel.classList.add('open');
1646        }, 10);
1647        
1648        console.log('Ozon Product Parser: Product details panel opened');
1649    }
1650
1651    // Создаем UI кнопок
1652    function createUI() {
1653        const container = document.createElement('div');
1654        container.className = 'ozon-parser-container';
1655        
1656        // Загружаем сохраненную позицию
1657        GM.getValue('ozon_parser_position', JSON.stringify({ top: 20, right: 20 })).then(posJson => {
1658            const pos = JSON.parse(posJson);
1659            container.style.top = pos.top + 'px';
1660            container.style.right = pos.right + 'px';
1661        });
1662        
1663        // Загружаем состояние сворачивания
1664        GM.getValue('ozon_parser_collapsed', 'false').then(collapsed => {
1665            if (collapsed === 'true') {
1666                container.classList.add('collapsed');
1667                buttonsWrapper.classList.add('hidden');
1668            }
1669        });
1670        
1671        // Кнопка сворачивания/разворачивания
1672        const toggleBtn = document.createElement('button');
1673        toggleBtn.className = 'ozon-parser-toggle-btn';
1674        toggleBtn.innerHTML = '📊';
1675        toggleBtn.title = 'Свернуть/Развернуть панель';
1676        
1677        // Обертка для кнопок
1678        const buttonsWrapper = document.createElement('div');
1679        buttonsWrapper.className = 'ozon-parser-buttons-wrapper';
1680        
1681        const parseBtn = document.createElement('button');
1682        parseBtn.className = 'ozon-parser-btn';
1683        parseBtn.textContent = 'Парсинг';
1684        parseBtn.addEventListener('click', (e) => {
1685            e.stopPropagation();
1686            showParseModal();
1687        });
1688        
1689        const pricesBtn = document.createElement('button');
1690        pricesBtn.className = 'ozon-parser-btn';
1691        pricesBtn.id = 'prices-btn';
1692        pricesBtn.style.background = 'linear-gradient(135deg, #dc3545 0%, #c82333 100%)';
1693        pricesBtn.style.boxShadow = '0 4px 12px rgba(220, 53, 69, 0.3)';
1694        pricesBtn.textContent = 'Цены';
1695        pricesBtn.addEventListener('click', (e) => {
1696            e.stopPropagation();
1697            showPricesModal();
1698        });
1699        
1700        const resultsBtn = document.createElement('button');
1701        resultsBtn.className = 'ozon-parser-btn';
1702        resultsBtn.id = 'results-btn';
1703        resultsBtn.style.background = 'linear-gradient(135deg, #28a745 0%, #1e7e34 100%)';
1704        resultsBtn.style.boxShadow = '0 4px 12px rgba(40, 167, 69, 0.3)';
1705        resultsBtn.textContent = 'Результаты';
1706        resultsBtn.addEventListener('click', (e) => {
1707            e.stopPropagation();
1708            showResultsModal();
1709        });
1710        
1711        buttonsWrapper.appendChild(parseBtn);
1712        buttonsWrapper.appendChild(pricesBtn);
1713        buttonsWrapper.appendChild(resultsBtn);
1714        
1715        container.appendChild(toggleBtn);
1716        container.appendChild(buttonsWrapper);
1717        
1718        // Обработчик сворачивания/разворачивания
1719        toggleBtn.addEventListener('click', async (e) => {
1720            e.stopPropagation();
1721            const isCollapsed = container.classList.toggle('collapsed');
1722            buttonsWrapper.classList.toggle('hidden');
1723            await GM.setValue('ozon_parser_collapsed', isCollapsed ? 'true' : 'false');
1724        });
1725        
1726        // Перетаскивание панели
1727        let isDragging = false;
1728        let currentX;
1729        let currentY;
1730        let initialX;
1731        let initialY;
1732        
1733        container.addEventListener('mousedown', (e) => {
1734            // Не начинаем перетаскивание, если кликнули на кнопку
1735            if (e.target.tagName === 'BUTTON') return;
1736            
1737            isDragging = true;
1738            initialX = e.clientX - container.offsetLeft;
1739            initialY = e.clientY - container.offsetTop;
1740            container.style.cursor = 'grabbing';
1741        });
1742        
1743        document.addEventListener('mousemove', (e) => {
1744            if (!isDragging) return;
1745            
1746            e.preventDefault();
1747            currentX = e.clientX - initialX;
1748            currentY = e.clientY - initialY;
1749            
1750            // Ограничиваем перемещение в пределах окна
1751            const maxX = window.innerWidth - container.offsetWidth;
1752            const maxY = window.innerHeight - container.offsetHeight;
1753            
1754            currentX = Math.max(0, Math.min(currentX, maxX));
1755            currentY = Math.max(0, Math.min(currentY, maxY));
1756            
1757            container.style.left = currentX + 'px';
1758            container.style.top = currentY + 'px';
1759            container.style.right = 'auto';
1760        });
1761        
1762        document.addEventListener('mouseup', async () => {
1763            if (isDragging) {
1764                isDragging = false;
1765                container.style.cursor = 'move';
1766                
1767                // Сохраняем позицию
1768                const rect = container.getBoundingClientRect();
1769                await GM.setValue('ozon_parser_position', JSON.stringify({
1770                    top: rect.top,
1771                    right: window.innerWidth - rect.right
1772                }));
1773            }
1774        });
1775        
1776        document.body.appendChild(container);
1777        console.log('Ozon Product Parser: UI created');
1778    }
1779
1780    // Показываем модальное окно для ввода запросов
1781    function showParseModal() {
1782        const modal = document.createElement('div');
1783        modal.className = 'ozon-parser-modal';
1784        
1785        const content = document.createElement('div');
1786        content.className = 'ozon-parser-modal-content';
1787        content.innerHTML = `
1788            <div class="ozon-parser-modal-header">Парсинг товаров Ozon</div>
1789            <div class="ozon-parser-modal-body">
1790                <div class="ozon-parser-info">
1791                    Введите поисковые запросы (каждый с новой строки). Парсер извлечет топ-16 товаров для каждого запроса.
1792                </div>
1793                <div style="margin-bottom: 15px;">
1794                    <label style="font-size: 14px; font-weight: 600; margin-bottom: 8px; display: block;">Скидка Ozon (%):</label>
1795                    <div style="display: flex; gap: 10px; align-items: center;">
1796                        <input type="number" class="ozon-parser-search" id="ozon-discount-input" placeholder="50" value="50" min="0" max="100" step="1" style="margin-bottom: 0; flex: 1;">
1797                        <button class="ozon-parser-btn" id="calculate-discount-btn" style="background: linear-gradient(135deg, #ff6b00 0%, #e65100 100%); box-shadow: 0 4px 12px rgba(255, 107, 0, 0.3); margin-bottom: 0;">Рассчитать автоматически</button>
1798                    </div>
1799                    <div style="font-size: 12px; color: #666; margin-top: 5px;">
1800                        Укажите среднюю скидку Ozon для расчета базовой цены (цены поручения). Например, если товар продается по 500₽ со скидкой 50%, то базовая цена = 1000₽.
1801                    </div>
1802                </div>
1803                <div style="margin-bottom: 15px;">
1804                    <button class="ozon-parser-btn" id="load-list-btn" style="width: 100%; margin-bottom: 10px;">Загрузить сохраненный список</button>
1805                </div>
1806                <textarea class="ozon-parser-textarea" placeholder="Например:&#10;гинкго билоба&#10;аргинин&#10;витамин д"></textarea>
1807                <div style="margin-top: 15px; display: flex; gap: 10px; align-items: center;">
1808                    <input type="text" class="ozon-parser-search" id="list-name-input" placeholder="Название списка (например: БАДы март 2024)" style="flex: 1; margin-bottom: 0;">
1809                    <button class="ozon-parser-btn secondary" id="save-list-btn">Сохранить список</button>
1810                </div>
1811                <div style="margin-top: 15px;">
1812                    <button class="ozon-parser-btn secondary" id="manage-costs-btn" style="width: 100%;">Управление расходами (себестоимость, комиссия, доставка)</button>
1813                </div>
1814                <div class="ozon-parser-progress" style="display: none;">
1815                    <div class="ozon-parser-progress-text">Обработка запросов...</div>
1816                    <div class="ozon-parser-progress-bar">
1817                        <div class="ozon-parser-progress-fill" style="width: 0%"></div>
1818                    </div>
1819                </div>
1820            </div>
1821            <div class="ozon-parser-modal-footer">
1822                <button class="ozon-parser-btn" id="cancel-parse-btn">Отмена</button>
1823                <button class="ozon-parser-btn" id="start-parsing-btn">Начать парсинг</button>
1824            </div>
1825        `;
1826        
1827        modal.appendChild(content);
1828        document.body.appendChild(modal);
1829
1830        // Закрытие по клику на фон
1831        modal.addEventListener('click', (e) => {
1832            if (e.target === modal) {
1833                modal.remove();
1834            }
1835        });
1836        
1837        // Обработчик кнопки отмены
1838        const cancelBtn = content.querySelector('#cancel-parse-btn');
1839        cancelBtn.addEventListener('click', () => {
1840            modal.remove();
1841        });
1842        
1843        // Обработчик кнопки расчета скидки
1844        const calculateDiscountBtn = content.querySelector('#calculate-discount-btn');
1845        calculateDiscountBtn.addEventListener('click', async () => {
1846            calculateDiscountBtn.disabled = true;
1847            calculateDiscountBtn.textContent = 'Расчет...';
1848            
1849            try {
1850                // Сохраняем флаг для автоматического расчета
1851                await GM.setValue('ozon_parser_calculate_discount', 'true');
1852                
1853                // Открываем страницу в новой вкладке
1854                await GM.openInTab('https://seller.ozon.ru/app/prices/control', false);
1855                
1856                // Ждем результата расчета
1857                let attempts = 0;
1858                const maxAttempts = 60; // 60 секунд максимум
1859                
1860                const checkInterval = setInterval(async () => {
1861                    attempts++;
1862                    const calculatedDiscount = await GM.getValue('ozon_parser_calculated_discount', null);
1863                    const calculateFlag = await GM.getValue('ozon_parser_calculate_discount', 'false');
1864                    
1865                    if (calculatedDiscount !== null && calculateFlag === 'false') {
1866                        // Расчет завершен
1867                        clearInterval(checkInterval);
1868                        
1869                        // Вставляем значение в поле
1870                        const discountInput = content.querySelector('#ozon-discount-input');
1871                        discountInput.value = parseFloat(calculatedDiscount).toFixed(1);
1872                        
1873                        // Очищаем временное значение
1874                        await GM.deleteValue('ozon_parser_calculated_discount');
1875                        
1876                        calculateDiscountBtn.disabled = false;
1877                        calculateDiscountBtn.textContent = 'Рассчитать автоматически';
1878                        
1879                        alert(`Скидка Ozon успешно рассчитана: ${parseFloat(calculatedDiscount).toFixed(1)}%`);
1880                    } else if (attempts >= maxAttempts) {
1881                        // Таймаут
1882                        clearInterval(checkInterval);
1883                        calculateDiscountBtn.disabled = false;
1884                        calculateDiscountBtn.textContent = 'Рассчитать автоматически';
1885                        alert('Не удалось рассчитать скидку. Попробуйте еще раз.');
1886                    }
1887                }, 1000);
1888            } catch (error) {
1889                console.error('Ozon Product Parser: Error calculating discount:', error);
1890                calculateDiscountBtn.disabled = false;
1891                calculateDiscountBtn.textContent = 'Рассчитать автоматически';
1892                alert('Ошибка при расчете скидки: ' + error.message);
1893            }
1894        });
1895
1896        // Обработчик кнопки сохранения списка
1897        const saveListBtn = content.querySelector('#save-list-btn');
1898        saveListBtn.addEventListener('click', async () => {
1899            const textarea = content.querySelector('.ozon-parser-textarea');
1900            const listNameInput = content.querySelector('#list-name-input');
1901            const queries = textarea.value.split('\n').filter(q => q.trim());
1902            const listName = listNameInput.value.trim();
1903            
1904            if (queries.length === 0) {
1905                alert('Пожалуйста, введите хотя бы один запрос');
1906                return;
1907            }
1908            
1909            if (!listName) {
1910                alert('Пожалуйста, введите название списка');
1911                return;
1912            }
1913            
1914            // Сохраняем список
1915            const savedListsJson = await GM.getValue('ozon_parser_saved_lists', '{}');
1916            const savedLists = JSON.parse(savedListsJson);
1917            
1918            savedLists[listName] = {
1919                queries: queries,
1920                createdAt: new Date().toISOString(),
1921                updatedAt: new Date().toISOString()
1922            };
1923            
1924            await GM.setValue('ozon_parser_saved_lists', JSON.stringify(savedLists));
1925            
1926            alert(`Список "${listName}" успешно сохранен!`);
1927            console.log(`Ozon Product Parser: List "${listName}" saved with ${queries.length} queries`);
1928        });
1929        
1930        // Обработчик кнопки загрузки списка
1931        const loadListBtn = content.querySelector('#load-list-btn');
1932        loadListBtn.addEventListener('click', async () => {
1933            await showLoadListModal(content);
1934        });
1935        
1936        // Обработчик кнопки управления расходами
1937        const manageCostsBtn = content.querySelector('#manage-costs-btn');
1938        manageCostsBtn.addEventListener('click', async () => {
1939            await showManageCostsModal();
1940        });
1941        
1942        // Обработчик кнопки парсинга
1943        const startBtn = content.querySelector('#start-parsing-btn');
1944        startBtn.addEventListener('click', async () => {
1945            const textarea = content.querySelector('.ozon-parser-textarea');
1946            const listNameInput = content.querySelector('#list-name-input');
1947            const ozonDiscountInput = content.querySelector('#ozon-discount-input');
1948            const queries = textarea.value.split('\n').filter(q => q.trim());
1949            const listName = listNameInput.value.trim() || 'Без названия';
1950            const ozonDiscount = parseFloat(ozonDiscountInput.value) || 50;
1951            
1952            if (queries.length === 0) {
1953                alert('Пожалуйста, введите хотя бы один запрос');
1954                return;
1955            }
1956            
1957            if (ozonDiscount < 0 || ozonDiscount > 100) {
1958                alert('Скидка Ozon должна быть от 0 до 100%');
1959                return;
1960            }
1961            
1962            // Сохраняем скидку Ozon
1963            await GM.setValue('ozon_parser_discount', ozonDiscount);
1964            
1965            startBtn.disabled = true;
1966            startBtn.textContent = 'Парсинг...';
1967            await startParsing(queries, listName, content);
1968        });
1969        
1970        console.log('Ozon Product Parser: Parse modal shown');
1971    }
1972
1973    // Показываем модальное окно для загрузки сохраненного списка
1974    async function showLoadListModal(parentContent) {
1975        const savedListsJson = await GM.getValue('ozon_parser_saved_lists', '{}');
1976        const savedLists = JSON.parse(savedListsJson);
1977        const listNames = Object.keys(savedLists);
1978        
1979        if (listNames.length === 0) {
1980            alert('Нет сохраненных списков');
1981            return;
1982        }
1983        
1984        const loadModal = document.createElement('div');
1985        loadModal.className = 'ozon-parser-modal';
1986        loadModal.style.zIndex = '10002';
1987        
1988        const loadContent = document.createElement('div');
1989        loadContent.className = 'ozon-parser-modal-content';
1990        loadContent.style.maxWidth = '600px';
1991        
1992        let listsHTML = '<div class="ozon-parser-modal-header">Выберите список</div><div class="ozon-parser-modal-body">';
1993        
1994        listNames.forEach(listName => {
1995            const list = savedLists[listName];
1996            const date = new Date(list.createdAt).toLocaleDateString('ru-RU');
1997            const queriesCount = list.queries.length;
1998            listsHTML += `
1999                <div style="padding: 15px; background: white; border: 2px solid #e0e0e0; border-radius: 8px; margin-bottom: 10px; display: flex; justify-content: space-between; align-items: center; transition: all 0.2s;" 
2000                     class="saved-list-item" data-list-name="${listName}">
2001                    <div style="flex: 1; cursor: pointer;" class="saved-list-info">
2002                        <div style="font-weight: 600; font-size: 16px; margin-bottom: 5px;">${listName}</div>
2003                        <div style="font-size: 13px; color: #666;">Запросов: ${queriesCount} | Создан: ${date}</div>
2004                    </div>
2005                    <button class="ozon-parser-btn" style="background: linear-gradient(135deg, #dc3545 0%, #c82333 100%); padding: 8px 16px; font-size: 13px; margin-left: 10px;" data-delete-list="${listName}">Удалить</button>
2006                </div>
2007            `;
2008        });
2009        
2010        listsHTML += '</div><div class="ozon-parser-modal-footer"><button class="ozon-parser-btn" id="close-load-modal">Отмена</button></div>';
2011        
2012        loadContent.innerHTML = listsHTML;
2013        loadModal.appendChild(loadContent);
2014        document.body.appendChild(loadModal);
2015        
2016        // Обработчик выбора списка (только для info блока)
2017        loadContent.querySelectorAll('.saved-list-info').forEach(info => {
2018            info.addEventListener('click', () => {
2019                const listItem = info.closest('.saved-list-item');
2020                const listName = listItem.getAttribute('data-list-name');
2021                const list = savedLists[listName];
2022                
2023                // Заполняем textarea
2024                const textarea = parentContent.querySelector('.ozon-parser-textarea');
2025                const listNameInput = parentContent.querySelector('#list-name-input');
2026                textarea.value = list.queries.join('\n');
2027                listNameInput.value = listName;
2028                
2029                loadModal.remove();
2030            });
2031            
2032            // Hover эффект
2033            const listItem = info.closest('.saved-list-item');
2034            info.addEventListener('mouseenter', () => {
2035                listItem.style.borderColor = '#005bff';
2036                listItem.style.background = '#f8f9fa';
2037            });
2038            info.addEventListener('mouseleave', () => {
2039                listItem.style.borderColor = '#e0e0e0';
2040                listItem.style.background = 'white';
2041            });
2042        });
2043        
2044        // Обработчик удаления списка
2045        loadContent.querySelectorAll('[data-delete-list]').forEach(deleteBtn => {
2046            deleteBtn.addEventListener('click', async (e) => {
2047                e.stopPropagation();
2048                const listName = deleteBtn.getAttribute('data-delete-list');
2049                
2050                if (!confirm(`Вы уверены, что хотите удалить список "${listName}"?`)) {
2051                    return;
2052                }
2053                
2054                // Удаляем список из savedLists
2055                delete savedLists[listName];
2056                await GM.setValue('ozon_parser_saved_lists', JSON.stringify(savedLists));
2057                
2058                // Также удаляем результаты парсинга для этого списка
2059                const listResultsJson = await GM.getValue('ozon_parser_list_results', '{}');
2060                const listResults = JSON.parse(listResultsJson);
2061                delete listResults[listName];
2062                await GM.setValue('ozon_parser_list_results', JSON.stringify(listResults));
2063                
2064                console.log(`Ozon Product Parser: List "${listName}" deleted from load modal`);
2065                
2066                // Удаляем элемент из DOM
2067                const listItem = deleteBtn.closest('.saved-list-item');
2068                listItem.remove();
2069                
2070                // Если списков не осталось, закрываем модальное окно
2071                const remainingLists = loadContent.querySelectorAll('.saved-list-item');
2072                if (remainingLists.length === 0) {
2073                    alert('Все списки удалены');
2074                    loadModal.remove();
2075                }
2076            });
2077        });
2078        
2079        // Закрытие
2080        loadContent.querySelector('#close-load-modal').addEventListener('click', () => {
2081            loadModal.remove();
2082        });
2083        
2084        loadModal.addEventListener('click', (e) => {
2085            if (e.target === loadModal) {
2086                loadModal.remove();
2087            }
2088        });
2089    }
2090
2091    // Показываем модальное окно управления расходами
2092    async function showManageCostsModal() {
2093        const costsJson = await GM.getValue('ozon_parser_costs', '{}');
2094        const costs = JSON.parse(costsJson);
2095        
2096        const modal = document.createElement('div');
2097        modal.className = 'ozon-parser-modal';
2098        modal.style.zIndex = '10002';
2099        
2100        const content = document.createElement('div');
2101        content.className = 'ozon-parser-modal-content';
2102        content.style.maxWidth = '800px';
2103        
2104        content.innerHTML = `
2105            <div class="ozon-parser-modal-header">Управление расходами</div>
2106            <div class="ozon-parser-modal-body">
2107                <div class="ozon-parser-info">
2108                    Укажите расходы для ваших товаров (SKU). Эти данные будут использоваться для расчета прибыли и оптимальной цены.
2109                </div>
2110                
2111                <!-- Блок загрузки файла -->
2112                <div style="margin-bottom: 20px; padding: 15px; background: #f8f9fa; border-radius: 8px;">
2113                    <label style="font-size: 14px; font-weight: 600; margin-bottom: 8px; display: block;">Загрузить данные из файла:</label>
2114                    <div style="display: flex; gap: 10px; align-items: center; margin-bottom: 10px;">
2115                        <input type="file" id="cost-file-input" accept=".csv,.json,.txt" style="flex: 1; padding: 8px; border: 2px solid #e0e0e0; border-radius: 8px;">
2116                        <button class="ozon-parser-btn" id="upload-costs-btn">Загрузить</button>
2117                    </div>
2118                    <details style="margin-top: 10px;">
2119                        <summary style="cursor: pointer; font-size: 12px; color: #666;">Формат файлов</summary>
2120                        <div style="font-size: 12px; color: #666; margin-top: 8px; line-height: 1.6;">
2121                            <strong>CSV/TXT:</strong> SKU,Себестоимость,Комиссия,Доставка<br>
2122                            Пример: 320244429,158.4,50,90<br><br>
2123                            <strong>JSON:</strong> {"SKU": {"cost": 158.4, "commission": 0.5, "delivery": 90}}<br>
2124                            Примечание: Комиссия в CSV указывается в процентах (50), в JSON - как десятичная дробь (0.5)
2125                        </div>
2126                    </details>
2127                </div>
2128                
2129                <div style="margin-bottom: 15px;">
2130                    <label style="font-size: 14px; font-weight: 600; margin-bottom: 8px; display: block;">Добавить новый товар:</label>
2131                    <div style="display: grid; grid-template-columns: 2fr 1fr 1fr 1fr auto; gap: 10px; align-items: end;">
2132                        <div>
2133                            <label style="font-size: 12px; color: #666;">SKU</label>
2134                            <input type="text" class="ozon-parser-search" id="new-sku" placeholder="320244429" style="margin-bottom: 0;">
2135                        </div>
2136                        <div>
2137                            <label style="font-size: 12px; color: #666;">Себестоимость (₽)</label>
2138                            <input type="number" class="ozon-parser-search" id="new-cost" placeholder="158.4" step="0.01" style="margin-bottom: 0;">
2139                        </div>
2140                        <div>
2141                            <label style="font-size: 12px; color: #666;">Комиссия (%)</label>
2142                            <input type="number" class="ozon-parser-search" id="new-commission" placeholder="50" step="0.1" style="margin-bottom: 0;">
2143                        </div>
2144                        <div>
2145                            <label style="font-size: 12px; color: #666;">Доставка (₽)</label>
2146                            <input type="number" class="ozon-parser-search" id="new-delivery" placeholder="90" step="0.01" style="margin-bottom: 0;">
2147                        </div>
2148                        <button class="ozon-parser-btn" id="add-cost-btn" style="margin-bottom: 0;">Добавить</button>
2149                    </div>
2150                </div>
2151                <div id="costs-list" style="max-height: 400px; overflow-y: auto;">
2152                    ${Object.keys(costs).length === 0 ? '<p style="text-align: center; color: #999;">Нет добавленных товаров</p>' : ''}
2153                </div>
2154            </div>
2155            <div class="ozon-parser-modal-footer">
2156                <button class="ozon-parser-btn" id="close-costs-modal">Закрыть</button>
2157            </div>
2158        `;
2159        
2160        modal.appendChild(content);
2161        document.body.appendChild(modal);
2162        
2163        // Функция для парсинга CSV
2164        function parseCSV(text) {
2165            const lines = text.trim().split('\n');
2166            const result = {};
2167            
2168            for (let i = 0; i < lines.length; i++) {
2169                const line = lines[i].trim();
2170                if (!line || line.startsWith('SKU')) continue; // Пропускаем заголовок
2171                
2172                const parts = line.split(',').map(p => p.trim());
2173                if (parts.length < 4) continue;
2174                
2175                // Удаляем кавычки из SKU, если они есть
2176                let sku = parts[0].replace(/^["']|["']$/g, '');
2177                const cost = parseFloat(parts[1]);
2178                const commission = parseFloat(parts[2]);
2179                const delivery = parseFloat(parts[3]);
2180                
2181                if (sku && !isNaN(cost) && !isNaN(commission) && !isNaN(delivery)) {
2182                    result[sku] = {
2183                        cost: cost,
2184                        commission: commission / 100, // Конвертируем проценты в десятичную дробь
2185                        delivery: delivery
2186                    };
2187                }
2188            }
2189            
2190            return result;
2191        }
2192        
2193        // Функция для парсинга JS-объекта из txt файла
2194        function parseJSObject(text) {
2195            try {
2196                // Удаляем возможные комментарии и лишние пробелы
2197                let cleanText = text.trim();
2198                
2199                // Если текст не начинается с {, добавляем открывающую скобку
2200                if (!cleanText.startsWith('{')) {
2201                    cleanText = '{' + cleanText;
2202                }
2203                
2204                // Если текст не заканчивается на }, добавляем закрывающую скобку
2205                if (!cleanText.endsWith('}')) {
2206                    // Удаляем последнюю запятую, если она есть
2207                    cleanText = cleanText.replace(/,\s*$/, '');
2208                    cleanText = cleanText + '}';
2209                }
2210                
2211                console.log('Ozon Product Parser: Attempting to parse JS object, length:', cleanText.length);
2212                
2213                // Используем eval для парсинга JS объекта (безопасно, т.к. это локальный файл пользователя)
2214                const result = eval('(' + cleanText + ')');
2215                
2216                // Валидация формата
2217                if (typeof result !== 'object' || result === null) {
2218                    throw new Error('Неверный формат: ожидается объект');
2219                }
2220                
2221                // Проверяем структуру данных
2222                for (const sku in result) {
2223                    const item = result[sku];
2224                    if (typeof item !== 'object' || item === null) {
2225                        throw new Error(`Неверный формат данных для SKU ${sku}. Ожидается объект с полями cost, commission, delivery`);
2226                    }
2227                    if (typeof item.cost !== 'number' || typeof item.commission !== 'number' || typeof item.delivery !== 'number') {
2228                        throw new Error(`Неверный формат данных для SKU ${sku}. Ожидается: { cost: число, commission: число (0-1), delivery: число }. Получено: ${JSON.stringify(item)}`);
2229                    }
2230                }
2231                
2232                console.log('Ozon Product Parser: Successfully parsed JS object with', Object.keys(result).length, 'items');
2233                return result;
2234            } catch (error) {
2235                console.error('Ozon Product Parser: JS object parse error:', error);
2236                throw new Error(`Ошибка парсинга JS-объекта: ${error.message}`);
2237            }
2238        }
2239        
2240        // Обработчик загрузки файла
2241        const uploadBtn = content.querySelector('#upload-costs-btn');
2242        uploadBtn.addEventListener('click', async () => {
2243            const fileInput = content.querySelector('#cost-file-input');
2244            const file = fileInput.files[0];
2245            
2246            if (!file) {
2247                alert('Пожалуйста, выберите файл');
2248                return;
2249            }
2250            
2251            try {
2252                const text = await file.text();
2253                let newCosts = {};
2254                
2255                console.log('Ozon Product Parser: Processing file:', file.name, 'Size:', file.size, 'bytes');
2256                
2257                if (file.name.endsWith('.json')) {
2258                    // Парсим JSON
2259                    try {
2260                        newCosts = JSON.parse(text);
2261                        console.log('Ozon Product Parser: Parsed JSON successfully');
2262                    } catch (jsonError) {
2263                        console.error('Ozon Product Parser: JSON parse error:', jsonError);
2264                        throw new Error(`Ошибка парсинга JSON: ${jsonError.message}. Проверьте, что файл содержит корректный JSON формат.`);
2265                    }
2266                    
2267                    // Валидация JSON формата
2268                    for (const sku in newCosts) {
2269                        const item = newCosts[sku];
2270                        if (typeof item !== 'object' || item === null) {
2271                            throw new Error(`Неверный формат данных для SKU ${sku}. Ожидается объект с полями cost, commission, delivery`);
2272                        }
2273                        if (typeof item.cost !== 'number' || typeof item.commission !== 'number' || typeof item.delivery !== 'number') {
2274                            throw new Error(`Неверный формат данных для SKU ${sku}. Ожидается: {"cost": число, "commission": число, "delivery": число}. Получено: ${JSON.stringify(item)}`);
2275                        }
2276                    }
2277                } else if (file.name.endsWith('.txt')) {
2278                    // Пытаемся определить формат: JS-объект или CSV
2279                    const trimmedText = text.trim();
2280                    console.log('Ozon Product Parser: Processing TXT file, first 100 chars:', trimmedText.substring(0, 100));
2281                    
2282                    if (trimmedText.startsWith('{') || trimmedText.includes('{')) {
2283                        // JS-объект формат
2284                        console.log('Ozon Product Parser: Detected JS object format');
2285                        newCosts = parseJSObject(text);
2286                    } else {
2287                        // CSV формат
2288                        console.log('Ozon Product Parser: Detected CSV format');
2289                        newCosts = parseCSV(text);
2290                    }
2291                } else {
2292                    // CSV формат для других расширений
2293                    console.log('Ozon Product Parser: Processing as CSV format');
2294                    newCosts = parseCSV(text);
2295                }
2296                
2297                console.log('Ozon Product Parser: Extracted', Object.keys(newCosts).length, 'items from file');
2298                
2299                if (Object.keys(newCosts).length === 0) {
2300                    alert('Не удалось извлечь данные из файла. Проверьте формат.\n\nОжидаемые форматы:\n\nCSV: SKU,Себестоимость,Комиссия,Доставка\nПример: 320244429,158.4,50,90\n\nJSON: {"SKU": {"cost": 158.4, "commission": 0.5, "delivery": 90}}');
2301                    return;
2302                }
2303                
2304                // Объединяем с существующими данными
2305                const mergedCosts = { ...costs, ...newCosts };
2306                await GM.setValue('ozon_parser_costs', JSON.stringify(mergedCosts));
2307                
2308                // Обновляем costs переменную
2309                Object.assign(costs, newCosts);
2310                
2311                alert(`Успешно загружено ${Object.keys(newCosts).length} товаров`);
2312                console.log('Ozon Product Parser: Successfully saved costs data');
2313                displayCostsList();
2314                
2315                // Очищаем input
2316                fileInput.value = '';
2317            } catch (error) {
2318                console.error('Ozon Product Parser: Error uploading costs file:', error);
2319                console.error('Ozon Product Parser: Error stack:', error.stack);
2320                console.error('Ozon Product Parser: Error name:', error.name);
2321                console.error('Ozon Product Parser: Error message:', error.message);
2322                alert('Ошибка при загрузке файла:\n\n' + error.message + '\n\nПроверьте формат файла и попробуйте снова.');
2323            }
2324        });
2325        
2326        // Функция для отображения списка расходов
2327        function displayCostsList() {
2328            const costsList = content.querySelector('#costs-list');
2329            
2330            if (Object.keys(costs).length === 0) {
2331                costsList.innerHTML = '<p style="text-align: center; color: #999;">Нет добавленных товаров</p>';
2332                return;
2333            }
2334            
2335            let html = '<div style="margin-bottom: 15px; display: flex; justify-content: space-between; align-items: center;">';
2336            html += '<div style="font-size: 14px; font-weight: 600;">Всего товаров: ' + Object.keys(costs).length + '</div>';
2337            html += '<button class="ozon-parser-btn" id="delete-all-costs-btn" style="background: linear-gradient(135deg, #dc3545 0%, #c82333 100%); padding: 8px 16px; font-size: 13px;">Удалить все расходы</button>';
2338            html += '</div>';
2339            html += '<table class="ozon-parser-results-table"><thead><tr><th>SKU</th><th>Себестоимость</th><th>Комиссия</th><th>Доставка</th><th>Действия</th></tr></thead><tbody>';
2340            
2341            Object.keys(costs).forEach(sku => {
2342                const cost = costs[sku];
2343                html += `
2344                    <tr>
2345                        <td><a href="https://www.ozon.ru/product/${sku}" target="_blank" class="ozon-parser-sku-link">${sku}</a></td>
2346                        <td>${cost.cost.toFixed(2)} ₽</td>
2347                        <td>${(cost.commission * 100).toFixed(2)}%</td>
2348                        <td>${cost.delivery.toFixed(2)} ₽</td>
2349                        <td>
2350                            <button class="ozon-parser-btn" data-edit-sku="${sku}" style="padding: 6px 12px; font-size: 12px; margin-right: 5px;">Изменить</button>
2351                            <button class="ozon-parser-btn" data-delete-sku="${sku}" style="background: linear-gradient(135deg, #dc3545 0%, #c82333 100%); padding: 6px 12px; font-size: 12px;">Удалить</button>
2352                        </td>
2353                    </tr>
2354                `;
2355            });
2356            
2357            html += '</tbody></table>';
2358            costsList.innerHTML = html;
2359            
2360            // Обработчик для кнопки удаления всех расходов
2361            const deleteAllBtn = costsList.querySelector('#delete-all-costs-btn');
2362            if (deleteAllBtn) {
2363                deleteAllBtn.addEventListener('click', async () => {
2364                    if (confirm('Вы уверены, что хотите удалить ВСЕ данные о расходах? Это действие нельзя отменить.')) {
2365                        // Очищаем все данные
2366                        Object.keys(costs).forEach(key => delete costs[key]);
2367                        await GM.setValue('ozon_parser_costs', JSON.stringify({}));
2368                        displayCostsList();
2369                        alert('Все данные о расходах удалены');
2370                    }
2371                });
2372            }
2373            
2374            // Обработчики для кнопок удаления
2375            costsList.querySelectorAll('[data-delete-sku]').forEach(btn => {
2376                btn.addEventListener('click', async () => {
2377                    const sku = btn.getAttribute('data-delete-sku');
2378                    if (confirm(`Удалить данные о расходах для SKU ${sku}?`)) {
2379                        delete costs[sku];
2380                        await GM.setValue('ozon_parser_costs', JSON.stringify(costs));
2381                        displayCostsList();
2382                    }
2383                });
2384            });
2385            
2386            // Обработчики для кнопок изменения
2387            costsList.querySelectorAll('[data-edit-sku]').forEach(btn => {
2388                btn.addEventListener('click', () => {
2389                    const sku = btn.getAttribute('data-edit-sku');
2390                    const cost = costs[sku];
2391                    
2392                    content.querySelector('#new-sku').value = sku;
2393                    content.querySelector('#new-cost').value = cost.cost;
2394                    content.querySelector('#new-commission').value = cost.commission * 100;
2395                    content.querySelector('#new-delivery').value = cost.delivery;
2396                    
2397                    content.querySelector('#new-sku').scrollIntoView({ behavior: 'smooth', block: 'center' });
2398                });
2399            });
2400        }
2401        
2402        displayCostsList();
2403        
2404        // Обработчик добавления нового товара
2405        const addBtn = content.querySelector('#add-cost-btn');
2406        addBtn.addEventListener('click', async () => {
2407            const sku = content.querySelector('#new-sku').value.trim();
2408            const cost = parseFloat(content.querySelector('#new-cost').value);
2409            const commission = parseFloat(content.querySelector('#new-commission').value);
2410            const delivery = parseFloat(content.querySelector('#new-delivery').value);
2411            
2412            if (!sku) {
2413                alert('Пожалуйста, введите SKU');
2414                return;
2415            }
2416            
2417            if (isNaN(cost) || cost < 0) {
2418                alert('Пожалуйста, введите корректную себестоимость');
2419                return;
2420            }
2421            
2422            if (isNaN(commission) || commission < 0 || commission > 100) {
2423                alert('Пожалуйста, введите корректную комиссию (0-100%)');
2424                return;
2425            }
2426            
2427            if (isNaN(delivery) || delivery < 0) {
2428                alert('Пожалуйста, введите корректную стоимость доставки');
2429                return;
2430            }
2431            
2432            costs[sku] = {
2433                cost: cost,
2434                commission: commission / 100, // Сохраняем как десятичную дробь
2435                delivery: delivery
2436            };
2437            
2438            await GM.setValue('ozon_parser_costs', JSON.stringify(costs));
2439            
2440            // Очищаем поля
2441            content.querySelector('#new-sku').value = '';
2442            content.querySelector('#new-cost').value = '';
2443            content.querySelector('#new-commission').value = '';
2444            content.querySelector('#new-delivery').value = '';
2445            
2446            displayCostsList();
2447        });
2448        
2449        // Закрытие
2450        content.querySelector('#close-costs-modal').addEventListener('click', () => {
2451            modal.remove();
2452        });
2453        
2454        modal.addEventListener('click', (e) => {
2455            if (e.target === modal) {
2456                modal.remove();
2457            }
2458        });
2459    }
2460
2461    // Извлекаем скидку со страницы управления ценами
2462    async function extractDiscountFromPage() {
2463        console.log('Ozon Product Parser: Extracting discount from prices page');
2464        
2465        // Ждем загрузки таблицы с ценами
2466        await waitForPricesTable();
2467        
2468        // Прокручиваем страницу вниз для загрузки цен (ленивая загрузка)
2469        console.log('Ozon Product Parser: Scrolling to load prices');
2470        for (let i = 0; i < 3; i++) {
2471            window.scrollBy(0, 500);
2472            await new Promise(resolve => setTimeout(resolve, 2000));
2473        }
2474        
2475        try {
2476            // Ищем цены по правильным классам
2477            // Цена продажи (с картой Ozon): index_priceByOzonCardCurrency_3DLKf
2478            // Базовая цена (цена поручения): index_priceAmount_3dfpL
2479            
2480            const salePriceElements = document.querySelectorAll('.index_priceByOzonCardCurrency_3DLKf');
2481            const basePriceElements = document.querySelectorAll('.index_priceAmount_3dfpL');
2482            
2483            console.log(`Ozon Product Parser: Found ${salePriceElements.length} sale prices and ${basePriceElements.length} base prices`);
2484            
2485            if (salePriceElements.length === 0 || basePriceElements.length === 0) {
2486                console.error('Ozon Product Parser: Could not find price elements on the page');
2487                alert('Не удалось найти цены на странице. Убедитесь, что у вас есть товары с ценами.');
2488                await GM.setValue('ozon_parser_calculate_discount', 'false');
2489                return;
2490            }
2491            
2492            // Собираем пары цен (базовая и продажная)
2493            let validDiscounts = [];
2494            
2495            // Ищем строки таблицы с обеими ценами
2496            const rows = document.querySelectorAll('tr');
2497            for (const row of rows) {
2498                const salePrice = row.querySelector('.index_priceByOzonCardCurrency_3DLKf');
2499                const basePrice = row.querySelector('.index_priceAmount_3dfpL');
2500                
2501                if (salePrice && basePrice) {
2502                    const salePriceText = salePrice.textContent.trim();
2503                    const basePriceText = basePrice.textContent.trim();
2504                    
2505                    // Парсим цены (убираем пробелы и символ рубля)
2506                    const salePriceValue = parseFloat(salePriceText.replace(/\s/g, '').replace('₽', '').replace(',', '.'));
2507                    const basePriceValue = parseFloat(basePriceText.replace(/\s/g, '').replace('₽', '').replace(',', '.'));
2508                    
2509                    if (!isNaN(salePriceValue) && !isNaN(basePriceValue) && basePriceValue > 0 && salePriceValue > 0) {
2510                        // Рассчитываем скидку: (базовая цена - цена продажи) / базовая цена * 100
2511                        const discountAmount = basePriceValue - salePriceValue;
2512                        const discountPercent = (discountAmount / basePriceValue) * 100;
2513                        
2514                        if (discountPercent > 0 && discountPercent < 100) {
2515                            validDiscounts.push({
2516                                basePrice: basePriceValue,
2517                                salePrice: salePriceValue,
2518                                discount: discountPercent
2519                            });
2520                            
2521                            console.log(`Ozon Product Parser: Base price: ${basePriceValue}₽, Sale price: ${salePriceValue}₽, Discount: ${discountPercent.toFixed(1)}%`);
2522                        }
2523                    }
2524                }
2525            }
2526            
2527            if (validDiscounts.length === 0) {
2528                console.error('Ozon Product Parser: No valid price pairs found');
2529                alert('Не удалось найти валидные пары цен на странице.');
2530                await GM.setValue('ozon_parser_calculate_discount', 'false');
2531                return;
2532            }
2533            
2534            // Рассчитываем среднюю скидку
2535            const avgDiscount = validDiscounts.reduce((sum, item) => sum + item.discount, 0) / validDiscounts.length;
2536            
2537            console.log(`Ozon Product Parser: Calculated average discount from ${validDiscounts.length} products: ${avgDiscount.toFixed(1)}%`);
2538            
2539            // Сохраняем рассчитанную скидку для передачи в основное окно
2540            await GM.setValue('ozon_parser_calculated_discount', avgDiscount);
2541            
2542            // Сбрасываем флаг расчета
2543            await GM.setValue('ozon_parser_calculate_discount', 'false');
2544            
2545            // Закрываем текущую вкладку
2546            window.close();
2547        } catch (error) {
2548            console.error('Ozon Product Parser: Error extracting discount:', error);
2549            alert('Ошибка при извлечении данных о скидке: ' + error.message);
2550            
2551            // Сбрасываем флаг расчета
2552            await GM.setValue('ozon_parser_calculate_discount', 'false');
2553        }
2554    }
2555
2556    // Ждем появления таблицы с ценами
2557    function waitForPricesTable() {
2558        return new Promise((resolve) => {
2559            const checkTable = () => {
2560                const rows = document.querySelectorAll('tr');
2561                if (rows.length > 0) {
2562                    console.log('Ozon Product Parser: Prices table found');
2563                    // Дополнительная задержка для полной загрузки данных
2564                    setTimeout(resolve, 3000);
2565                } else {
2566                    setTimeout(checkTable, 1000);
2567                }
2568            };
2569            checkTable();
2570        });
2571    }
2572
2573    // Прокручиваем страницу для подгрузки товаров
2574    async function scrollToLoadProducts(targetCount = 20) {
2575        console.log(`Ozon Product Parser: Scrolling to load ${targetCount} products`);
2576        
2577        const table = document.querySelector('#mpstat-ozone-search-result table');
2578        if (!table) {
2579            console.error('Ozon Product Parser: Table not found for scrolling');
2580            return;
2581        }
2582        
2583        let previousRowCount = 0;
2584        let attempts = 0;
2585        const maxAttempts = 10;
2586        
2587        while (attempts < maxAttempts) {
2588            const rows = table.querySelectorAll('tbody tr');
2589            const currentRowCount = rows.length;
2590            
2591            console.log(`Ozon Product Parser: Current row count: ${currentRowCount}, target: ${targetCount}`);
2592            
2593            if (currentRowCount >= targetCount) {
2594                console.log(`Ozon Product Parser: Loaded ${currentRowCount} products`);
2595                break;
2596            }
2597            
2598            // Если количество строк не изменилось, значит больше товаров нет
2599            if (currentRowCount === previousRowCount && attempts > 2) {
2600                console.log(`Ozon Product Parser: No more products to load (${currentRowCount} total)`);
2601                break;
2602            }
2603            
2604            previousRowCount = currentRowCount;
2605            
2606            // Прокручиваем к последней строке таблицы
2607            const lastRow = rows[rows.length - 1];
2608            if (lastRow) {
2609                lastRow.scrollIntoView({ behavior: 'smooth', block: 'end' });
2610            }
2611            
2612            // Также прокручиваем окно вниз
2613            window.scrollBy(0, 500);
2614            
2615            // Ждем подгрузки новых товаров
2616            await new Promise(resolve => setTimeout(resolve, 2000));
2617            attempts++;
2618        }
2619        
2620        console.log(`Ozon Product Parser: Scrolling completed after ${attempts} attempts`);
2621    }
2622
2623    // Начинаем парсинг
2624    async function startParsing(queries, listName, modalContent) {
2625        const progressDiv = modalContent.querySelector('.ozon-parser-progress');
2626        progressDiv.style.display = 'block';
2627        
2628        // Сохраняем список запросов и название списка
2629        await GM.setValue('ozon_parser_queries', JSON.stringify(queries));
2630        await GM.setValue('ozon_parser_current_list_name', listName);
2631        await GM.setValue('ozon_parser_current_index', 0);
2632        await GM.setValue('ozon_parser_active', 'true');
2633        console.log(`Ozon Product Parser: Starting parsing process for list "${listName}"`);
2634        
2635        // Переходим к первому запросу
2636        const firstQuery = queries[0].trim();
2637        const searchUrl = `https://www.ozon.ru/search/?text=${encodeURIComponent(firstQuery)}&from_global=true`;
2638        window.location.href = searchUrl;
2639    }
2640
2641    // Продолжаем парсинг после загрузки страницы
2642    async function continueParsingIfActive() {
2643        const isActive = await GM.getValue('ozon_parser_active', 'false');
2644        if (isActive !== 'true') {
2645            return;
2646        }
2647        
2648        console.log('Ozon Product Parser: Continuing parsing process');
2649        
2650        // Ждем появления таблицы
2651        await waitForTable();
2652        
2653        // Получаем текущее состояние
2654        const queriesJson = await GM.getValue('ozon_parser_queries', '[]');
2655        const queries = JSON.parse(queriesJson);
2656        const currentIndex = await GM.getValue('ozon_parser_current_index', 0);
2657        const currentListName = await GM.getValue('ozon_parser_current_list_name', 'Без названия');
2658        
2659        // Получаем результаты для текущего списка
2660        const allListResultsJson = await GM.getValue('ozon_parser_list_results', '{}');
2661        const allListResults = JSON.parse(allListResultsJson);
2662        
2663        if (!allListResults[currentListName]) {
2664            allListResults[currentListName] = {
2665                queries: {},
2666                createdAt: new Date().toISOString(),
2667                updatedAt: new Date().toISOString()
2668            };
2669        }
2670        
2671        if (currentIndex >= queries.length) {
2672            // Парсинг завершен
2673            await GM.setValue('ozon_parser_active', 'false');
2674            
2675            // Обновляем дату последнего обновления списка
2676            allListResults[currentListName].updatedAt = new Date().toISOString();
2677            await GM.setValue('ozon_parser_list_results', JSON.stringify(allListResults));
2678            
2679            console.log('Ozon Product Parser: Parsing completed');
2680            return;
2681        }
2682        
2683        const currentQuery = queries[currentIndex].trim();
2684        console.log(`Ozon Product Parser: Processing query ${currentIndex + 1}/${queries.length}: "${currentQuery}"`);
2685        
2686        // Парсим данные текущей страницы
2687        const products = await parseProducts();
2688        allListResults[currentListName].queries[currentQuery] = products;
2689        
2690        // Анализируем высокие цены
2691        await analyzeHighPrices(currentQuery, products);
2692        
2693        // Сохраняем результаты
2694        await GM.setValue('ozon_parser_list_results', JSON.stringify(allListResults));
2695        console.log(`Ozon Product Parser: Parsed ${products.length} products for "${currentQuery}" in list "${currentListName}"`);
2696        
2697        // Переходим к следующему запросу
2698        const nextIndex = currentIndex + 1;
2699        await GM.setValue('ozon_parser_current_index', nextIndex);
2700        
2701        if (nextIndex < queries.length) {
2702            // Есть еще запросы - переходим к следующему
2703            const nextQuery = queries[nextIndex].trim();
2704            const searchUrl = `https://www.ozon.ru/search/?text=${encodeURIComponent(nextQuery)}&from_global=true`;
2705            setTimeout(() => {
2706                window.location.href = searchUrl;
2707            }, 2000); // Небольшая задержка между запросами
2708        } else {
2709            // Все запросы обработаны
2710            await GM.setValue('ozon_parser_active', 'false');
2711            
2712            // Обновляем дату последнего обновления списка
2713            allListResults[currentListName].updatedAt = new Date().toISOString();
2714            await GM.setValue('ozon_parser_list_results', JSON.stringify(allListResults));
2715            
2716            console.log('Ozon Product Parser: All queries processed');
2717            
2718            // Показываем результаты
2719            setTimeout(() => {
2720                showResultsModal();
2721            }, 1000);
2722        }
2723    }
2724
2725    // Продолжаем расчет скидки после загрузки страницы
2726    async function continueDiscountCalculationIfActive() {
2727        const shouldCalculate = await GM.getValue('ozon_parser_calculate_discount', 'false');
2728        if (shouldCalculate === 'true') {
2729            await GM.setValue('ozon_parser_calculate_discount', 'false');
2730            console.log('Ozon Product Parser: Continuing discount calculation');
2731            await extractDiscountFromPage();
2732        }
2733    }
2734
2735    // Ждем появления таблицы
2736    function waitForTable() {
2737        return new Promise((resolve) => {
2738            const checkTable = () => {
2739                const table = document.querySelector('#mpstat-ozone-search-result table tbody');
2740                if (table && table.querySelectorAll('tr').length > 0) {
2741                    console.log('Ozon Product Parser: Table found');
2742                    // Дополнительная задержка для полной загрузки данных
2743                    setTimeout(resolve, 5000);
2744                } else {
2745                    setTimeout(checkTable, 1000);
2746                }
2747            };
2748            checkTable();
2749        });
2750    }
2751
2752    // Прокручиваем страницу для подгрузки товаров
2753    async function scrollToLoadProducts(targetCount = 20) {
2754        console.log(`Ozon Product Parser: Scrolling to load ${targetCount} products`);
2755        
2756        const table = document.querySelector('#mpstat-ozone-search-result table');
2757        if (!table) {
2758            console.error('Ozon Product Parser: Table not found for scrolling');
2759            return;
2760        }
2761        
2762        let previousRowCount = 0;
2763        let attempts = 0;
2764        const maxAttempts = 10;
2765        
2766        while (attempts < maxAttempts) {
2767            const rows = table.querySelectorAll('tbody tr');
2768            const currentRowCount = rows.length;
2769            
2770            console.log(`Ozon Product Parser: Current row count: ${currentRowCount}, target: ${targetCount}`);
2771            
2772            if (currentRowCount >= targetCount) {
2773                console.log(`Ozon Product Parser: Loaded ${currentRowCount} products`);
2774                break;
2775            }
2776            
2777            // Если количество строк не изменилось, значит больше товаров нет
2778            if (currentRowCount === previousRowCount && attempts > 2) {
2779                console.log(`Ozon Product Parser: No more products to load (${currentRowCount} total)`);
2780                break;
2781            }
2782            
2783            previousRowCount = currentRowCount;
2784            
2785            // Прокручиваем к последней строке таблицы
2786            const lastRow = rows[rows.length - 1];
2787            if (lastRow) {
2788                lastRow.scrollIntoView({ behavior: 'smooth', block: 'end' });
2789            }
2790            
2791            // Также прокручиваем окно вниз
2792            window.scrollBy(0, 500);
2793            
2794            // Ждем подгрузки новых товаров
2795            await new Promise(resolve => setTimeout(resolve, 2000));
2796            attempts++;
2797        }
2798        
2799        console.log(`Ozon Product Parser: Scrolling completed after ${attempts} attempts`);
2800    }
2801
2802    // Парсим товары из таблицы
2803    async function parseProducts() {
2804        const table = document.querySelector('#mpstat-ozone-search-result table');
2805        if (!table) {
2806            console.error('Ozon Product Parser: Table not found');
2807            return [];
2808        }
2809        
2810        // Прокручиваем страницу для подгрузки товаров
2811        await scrollToLoadProducts(20);
2812        
2813        const rows = table.querySelectorAll('tbody tr');
2814        const products = [];
2815        const maxProducts = Math.min(20, rows.length);
2816        
2817        // Получаем названия товаров из карточек на странице
2818        const productLinks = document.querySelectorAll('a[href*="/product/"]');
2819        const productNames = new Map();
2820        
2821        console.log(`Ozon Product Parser: Found ${productLinks.length} product links`);
2822        
2823        productLinks.forEach(link => {
2824            const href = link.getAttribute('href');
2825            const skuMatch = href.match(/\/product\/[^/]+-(\d+)/);
2826            if (skuMatch) {
2827                const sku = skuMatch[1];
2828                // Ищем название в родительском элементе
2829                const parent = link.closest('.tile-root') || link.closest('[data-index]');
2830                if (parent) {
2831                    const nameElement = parent.querySelector('.tsBody500Medium');
2832                    if (nameElement && nameElement.textContent.trim().length > 10) {
2833                        productNames.set(sku, nameElement.textContent.trim());
2834                    }
2835                }
2836            }
2837        });
2838        
2839        console.log(`Ozon Product Parser: Extracted ${productNames.size} product names`);
2840        
2841        // Функция для извлечения количества единиц из названия
2842        function extractQuantity(name) {
2843            if (!name) return null;
2844            
2845            // Ищем паттерны: "120 капсул", "60 таблеток", "180шт", "90 шт"
2846            const patterns = [
2847                /(\d+)\s*(?:капсул|капс|caps)/i,
2848                /(\d+)\s*(?:таблеток|табл|tablets|tabs)/i,
2849                /(\d+)\s*(?:штук|шт|pcs|pieces)/i,
2850                /(\d+)\s*(?:порций|servings)/i
2851            ];
2852            
2853            for (const pattern of patterns) {
2854                const match = name.match(pattern);
2855                if (match) {
2856                    const quantity = parseInt(match[1]);
2857                    if (quantity > 0 && quantity <= 1000) { // Разумные пределы
2858                        return quantity;
2859                    }
2860                }
2861            }
2862            
2863            return null;
2864        }
2865        
2866        for (let i = 0; i < maxProducts; i++) {
2867            const row = rows[i];
2868            const cells = row.querySelectorAll('td');
2869            if (cells.length < 8) continue;
2870            
2871            const position = cells[0]?.textContent.trim() || '';
2872            const sku = cells[2]?.textContent.trim() || '';
2873            const brand = cells[3]?.textContent.trim() || '';
2874            const priceText = cells[4]?.textContent.trim() || '';
2875            const revenueText = cells[6]?.textContent.trim() || '';
2876            const ordersText = cells[7]?.textContent.trim() || '';
2877            
2878            // Получаем название товара из карточки
2879            const name = productNames.get(sku) || '';
2880            
2881            // Извлекаем количество единиц
2882            const quantity = extractQuantity(name);
2883            
2884            // Проверяем, есть ли товар от GLS или Skinphoria
2885            const isTargetBrand = brand.includes('GLS Pharmaceuticals') || brand.includes('Skinphoria');
2886            
2887            // Парсим числовые значения
2888            const price = parseFloat(priceText.replace(/[^\d]/g, '')) || 0;
2889            const revenue = parseFloat(revenueText.replace(/[^\d]/g, '')) || 0;
2890            const orders = parseInt(ordersText.replace(/[^\d]/g, '')) || 0;
2891            
2892            // Рассчитываем цену за единицу
2893            const pricePerUnit = quantity && price > 0 ? price / quantity : null;
2894            
2895            products.push({
2896                position: parseInt(position) || (i + 1),
2897                sku,
2898                name,
2899                brand,
2900                price,
2901                revenue,
2902                orders,
2903                isTargetBrand,
2904                quantity,
2905                pricePerUnit
2906            });
2907        }
2908        
2909        // Сортируем по убыванию выручки
2910        products.sort((a, b) => b.revenue - a.revenue);
2911        console.log(`Ozon Product Parser: Parsed ${products.length} products, ${products.filter(p => p.isTargetBrand).length} from target brands`);
2912        return products;
2913    }
2914    
2915    // Анализируем высокие цены после парсинга
2916    async function analyzeHighPrices(query, products) {
2917        console.log(`Ozon Product Parser: Analyzing high prices for query "${query}"`);
2918        
2919        // Получаем топ-5 конкурентов по выручке
2920        const competitors = products.filter(p => !p.isTargetBrand && p.price > 0);
2921        const top5Competitors = competitors.slice(0, 5);
2922        
2923        if (top5Competitors.length === 0) {
2924            console.log('Ozon Product Parser: No competitors found for high price analysis');
2925            return;
2926        }
2927        
2928        // Находим минимальную цену среди топ-5 конкурентов
2929        const minCompetitorPrice = Math.min(...top5Competitors.map(p => p.price));
2930        console.log(`Ozon Product Parser: Min competitor price in top-5: ${minCompetitorPrice}`);
2931        
2932        // Проверяем наши товары
2933        const ourProducts = products.filter(p => p.isTargetBrand);
2934        const highPriceProducts = [];
2935        
2936        for (const product of ourProducts) {
2937            if (product.price > 0) {
2938                const priceDiff = ((product.price - minCompetitorPrice) / minCompetitorPrice) * 100;
2939                
2940                if (priceDiff > 10) {
2941                    console.log(`Ozon Product Parser: High price detected - SKU ${product.sku}: ${product.price}₽ vs ${minCompetitorPrice}₽ (+${priceDiff.toFixed(1)}%)`);
2942                    
2943                    highPriceProducts.push({
2944                        sku: product.sku,
2945                        name: product.name,
2946                        ourPrice: product.price,
2947                        competitorPrice: minCompetitorPrice,
2948                        query: query
2949                    });
2950                }
2951            }
2952        }
2953        
2954        if (highPriceProducts.length > 0) {
2955            // Добавляем в общий список высоких цен
2956            const existingDataJson = await GM.getValue('ozon_parser_high_price', '[]');
2957            const existingData = JSON.parse(existingDataJson);
2958            
2959            // Удаляем дубликаты по SKU + query
2960            const existingKeys = new Set(existingData.map(item => `${item.sku}_${item.query}`));
2961            const newProducts = highPriceProducts.filter(item => !existingKeys.has(`${item.sku}_${item.query}`));
2962            
2963            if (newProducts.length > 0) {
2964                const updatedData = [...existingData, ...newProducts];
2965                await GM.setValue('ozon_parser_high_price', JSON.stringify(updatedData));
2966                console.log(`Ozon Product Parser: Added ${newProducts.length} products to high price list`);
2967                
2968                // Обновляем счетчик
2969                await updateHighPriceCounter();
2970            }
2971        }
2972    }
2973
2974    // Анализируем данные для запроса
2975    async function analyzeProducts(products) {
2976        console.log('Ozon Product Parser: Starting analysis for', products.length, 'products');
2977        
2978        if (!products || products.length === 0) {
2979            console.error('Ozon Product Parser: No products to analyze');
2980            return null;
2981        }
2982
2983        try {
2984            // Получаем данные о расходах и скидке Ozon
2985            const costsJson = await GM.getValue('ozon_parser_costs', '{}');
2986            const costs = JSON.parse(costsJson);
2987            const ozonDiscount = await GM.getValue('ozon_parser_discount', 50);
2988            console.log(`Ozon Product Parser: Loaded costs for ${Object.keys(costs).length} SKUs, Ozon discount: ${ozonDiscount}%`);
2989
2990            // Общая выручка и заказы
2991            const totalRevenue = products.reduce((sum, p) => sum + p.revenue, 0);
2992            const totalOrders = products.reduce((sum, p) => sum + p.orders, 0);
2993
2994            // Топ-5 товаров по выручке
2995            const topProducts = products.slice(0, 5).map(p => ({
2996                name: p.name,
2997                brand: p.brand,
2998                price: p.price,
2999                revenue: p.revenue,
3000                orders: p.orders,
3001                position: p.position,
3002                revenueShare: ((p.revenue / totalRevenue) * 100).toFixed(1)
3003            }));
3004
3005            // Ценовые сегменты
3006            const prices = products.map(p => p.price).filter(p => p > 0);
3007            const minPrice = Math.min(...prices);
3008            const maxPrice = Math.max(...prices);
3009            const priceRange = maxPrice - minPrice;
3010            const segmentSize = priceRange / 4;
3011
3012            const priceSegments = [
3013                { min: minPrice, max: minPrice + segmentSize, name: 'Низкий' },
3014                { min: minPrice + segmentSize, max: minPrice + segmentSize * 2, name: 'Средний-' },
3015                { min: minPrice + segmentSize * 2, max: minPrice + segmentSize * 3, name: 'Средний+' },
3016                { min: minPrice + segmentSize * 3, max: maxPrice, name: 'Высокий' }
3017            ];
3018
3019            const segments = priceSegments.map(segment => {
3020                const segmentProducts = products.filter(p => 
3021                    p.price >= segment.min && p.price <= segment.max
3022                );
3023                const segmentRevenue = segmentProducts.reduce((sum, p) => sum + p.revenue, 0);
3024                const segmentOrders = segmentProducts.reduce((sum, p) => sum + p.orders, 0);
3025
3026                return {
3027                    name: segment.name,
3028                    priceRange: `${Math.round(segment.min)} - ${Math.round(segment.max)}`,
3029                    count: segmentProducts.length,
3030                    revenue: segmentRevenue,
3031                    orders: segmentOrders,
3032                    revenueShare: ((segmentRevenue / totalRevenue) * 100).toFixed(1),
3033                    avgPrice: segmentProducts.length > 0 
3034                        ? Math.round(segmentProducts.reduce((sum, p) => sum + p.price, 0) / segmentProducts.length)
3035                        : 0
3036                };
3037            }).filter(s => s.count > 0);
3038
3039            // Расчет эластичности запроса
3040            const competitors = products.filter(p => !p.isTargetBrand);
3041            let elasticity = null;
3042            let elasticityInterpretation = '';
3043            
3044            if (competitors.length >= 5) {
3045                const logPrices = competitors.map(p => Math.log(p.price + 1));
3046                const logOrders = competitors.map(p => Math.log(p.orders + 1));
3047                
3048                function simpleRegression(x, y) {
3049                    const n = x.length;
3050                    const sumX = x.reduce((a, b) => a + b, 0);
3051                    const sumY = y.reduce((a, b) => a + b, 0);
3052                    const sumXY = x.reduce((a, b, i) => a + b * y[i], 0);
3053                    const sumX2 = x.reduce((a, b) => a + b * b, 0);
3054                    const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
3055                    return slope;
3056                }
3057                
3058                const rawElasticity = simpleRegression(logPrices, logOrders);
3059                const priorElasticity = -1.2;
3060                const priorWeight = 0.2;
3061                elasticity = rawElasticity * (1 - priorWeight) + priorElasticity * priorWeight;
3062                
3063                console.log(`Ozon Product Parser: Raw elasticity: ${rawElasticity.toFixed(2)}, Bayesian adjusted: ${elasticity.toFixed(2)}`);
3064                
3065                if (elasticity < -1.5) {
3066                    elasticityInterpretation = 'Высокая эластичность - спрос очень чувствителен к цене. Снижение цены сильно увеличит продажи.';
3067                } else if (elasticity < -0.8) {
3068                    elasticityInterpretation = 'Средняя эластичность - спрос умеренно реагирует на изменение цены.';
3069                } else if (elasticity < 0) {
3070                    elasticityInterpretation = 'Низкая эластичность - спрос слабо зависит от цены. Можно повышать цену.';
3071                } else {
3072                    elasticityInterpretation = 'Аномальная эластичность - возможно недостаточно данных для анализа.';
3073                }
3074            }
3075
3076            // Функция для расчета прогноза
3077            function calculateForecast(newPrice, currentPrice, currentOrders, currentRevenue, productCosts) {
3078                const priceChange = (newPrice - currentPrice) / currentPrice;
3079                const usedElasticity = elasticity !== null ? elasticity : -1.2;
3080                const ordersChange = usedElasticity * priceChange;
3081                
3082                const forecastOrders = Math.round(currentOrders * (1 + ordersChange));
3083                const forecastRevenue = Math.round(forecastOrders * newPrice);
3084                
3085                let forecastProfit = null;
3086                let currentProfit = null;
3087                let profitChange = null;
3088                
3089                if (productCosts) {
3090                    const basePrice = newPrice / (1 - ozonDiscount / 100);
3091                    const sellerRevenue = basePrice * (1 - productCosts.commission) * forecastOrders;
3092                    const expenses = (productCosts.cost + productCosts.delivery) * forecastOrders;
3093                    forecastProfit = sellerRevenue - expenses;
3094                    
3095                    const currentBasePrice = currentPrice / (1 - ozonDiscount / 100);
3096                    const currentSellerRevenue = currentBasePrice * (1 - productCosts.commission) * currentOrders;
3097                    const currentExpenses = (productCosts.cost + productCosts.delivery) * currentOrders;
3098                    currentProfit = currentSellerRevenue - currentExpenses;
3099                    
3100                    profitChange = currentProfit > 0 ? Math.round(((forecastProfit - currentProfit) / currentProfit) * 100) : 0;
3101                }
3102                
3103                return {
3104                    orders: forecastOrders,
3105                    ordersChange: Math.round(ordersChange * 100),
3106                    revenue: forecastRevenue,
3107                    revenueChange: Math.round(((forecastRevenue - currentRevenue) / currentRevenue) * 100),
3108                    profit: forecastProfit,
3109                    profitChange: profitChange
3110                };
3111            }
3112
3113            // Функция для расчета рекомендованной цены для конкретного товара
3114            function calculatePriceForProduct(targetProduct, allProducts) {
3115                console.log(`Calculating price for SKU ${targetProduct.sku}`);
3116                
3117                const productCosts = costs[targetProduct.sku];
3118                
3119                // Находим конкурентов
3120                const positionRange = 5;
3121                const nearbyProducts = allProducts.filter(p => 
3122                    !p.isTargetBrand && 
3123                    Math.abs(p.position - targetProduct.position) <= positionRange &&
3124                    p.price > 0 && p.orders > 0
3125                );
3126                
3127                const referenceProducts = nearbyProducts.length >= 3 
3128                    ? nearbyProducts 
3129                    : allProducts.filter(p => !p.isTargetBrand && p.price > 0 && p.orders > 0).slice(0, 10);
3130                
3131                if (referenceProducts.length === 0) {
3132                    return null;
3133                }
3134                
3135                // Рассчитываем базовую оптимальную цену на основе конкурентов
3136                let weightedSum = 0;
3137                let weightSum = 0;
3138                referenceProducts.forEach(p => {
3139                    const weight = p.revenue;
3140                    weightedSum += p.price * weight;
3141                    weightSum += weight;
3142                });
3143                const weightedPrice = weightSum > 0 ? weightedSum / weightSum : 0;
3144                
3145                const productsWithConversion = referenceProducts.map(p => ({
3146                    ...p,
3147                    conversion: p.orders / p.price
3148                })).sort((a, b) => b.conversion - a.conversion);
3149                const topConversionPrice = productsWithConversion.length > 0 
3150                    ? productsWithConversion.slice(0, 3).reduce((sum, p) => sum + p.price, 0) / 3
3151                    : 0;
3152                
3153                const top10Prices = referenceProducts.slice(0, 10).map(p => p.price).sort((a, b) => a - b);
3154                const medianPrice = top10Prices.length > 0 
3155                    ? top10Prices[Math.floor(top10Prices.length / 2)]
3156                    : 0;
3157
3158                const marketOptimalPrice = (weightedPrice * 0.4 + topConversionPrice * 0.3 + medianPrice * 0.3);
3159                
3160                // Рассчитываем RPI
3161                const competitorPrices = allProducts.filter(p => !p.isTargetBrand && p.price > 0);
3162                let avgCompetitorPrice = 0;
3163                if (competitorPrices.length > 0) {
3164                    let weightedPriceSum = 0;
3165                    let weightSum = 0;
3166                    competitorPrices.forEach(comp => {
3167                        const weight = comp.revenue;
3168                        weightedPriceSum += comp.price * weight;
3169                        weightSum += weight;
3170                    });
3171                    avgCompetitorPrice = weightSum > 0 ? weightedPriceSum / weightSum : targetProduct.price;
3172                }
3173                
3174                const rpi = avgCompetitorPrice > 0 ? (targetProduct.price / avgCompetitorPrice) * 100 : 100;
3175                
3176                // Используем эластичность для оптимизации прибыли
3177                const usedElasticity = elasticity !== null ? elasticity : -1.2;
3178                
3179                // Функция для расчета ожидаемой прибыли при заданной цене
3180                function calculateExpectedProfit(price) {
3181                    if (!productCosts) return null;
3182                    
3183                    const priceChange = (price - targetProduct.price) / targetProduct.price;
3184                    const ordersChange = usedElasticity * priceChange;
3185                    const expectedOrders = Math.max(1, targetProduct.orders * (1 + ordersChange));
3186                    
3187                    const basePrice = price / (1 - ozonDiscount / 100);
3188                    const sellerRevenue = basePrice * (1 - productCosts.commission) * expectedOrders;
3189                    const expenses = (productCosts.cost + productCosts.delivery) * expectedOrders;
3190                    
3191                    return sellerRevenue - expenses;
3192                }
3193                
3194                // Рассчитываем 4 варианта цен с учетом эластичности и максимизации прибыли
3195                // Захват рынка: ищем оптимальную цену ниже текущей для РОСТА прибыли
3196                // Цена ОБЯЗАТЕЛЬНО ниже текущей, но прибыль должна РАСТИ за счет увеличения объема
3197                let marketCapturePrice = targetProduct.price;
3198                if (productCosts) {
3199                    const currentProfit = calculateExpectedProfit(targetProduct.price);
3200                    let bestPrice = targetProduct.price;
3201                    let maxProfit = currentProfit;
3202                    
3203                    // Ищем оптимум между -30% и -1% от текущей цены
3204                    for (let multiplier = 0.70; multiplier < 0.99; multiplier += 0.01) {
3205                        const testPrice = targetProduct.price * multiplier;
3206                        const testProfit = calculateExpectedProfit(testPrice);
3207                        
3208                        // Выбираем цену с максимальной прибылью (больше текущей)
3209                        if (testProfit > maxProfit) {
3210                            maxProfit = testProfit;
3211                            bestPrice = testPrice;
3212                        }
3213                    }
3214                    
3215                    // Если нашли цену с большей прибылью - используем её
3216                    if (maxProfit > currentProfit) {
3217                        marketCapturePrice = Math.round(bestPrice);
3218                    } else {
3219                        // Если не нашли - используем безопасное снижение на 5%
3220                        marketCapturePrice = Math.round(targetProduct.price * 0.95);
3221                    }
3222                } else {
3223                    // Если нет данных о расходах, снижаем на 5%
3224                    marketCapturePrice = Math.round(targetProduct.price * 0.95);
3225                }
3226                
3227                // Агрессивная: ищем цену для максимизации прибыли с небольшим снижением
3228                // Допускаем падение прибыли до -2%, но стремимся к росту
3229                let aggressivePrice = targetProduct.price;
3230                if (productCosts) {
3231                    const currentProfit = calculateExpectedProfit(targetProduct.price);
3232                    let maxProfit = currentProfit;
3233                    const minAcceptableProfit = currentProfit * 0.98; // Допускаем падение до -2%
3234                    
3235                    // Ищем оптимум между -10% и +5% от текущей цены
3236                    for (let multiplier = 0.90; multiplier <= 1.05; multiplier += 0.01) {
3237                        const testPrice = targetProduct.price * multiplier;
3238                        const testProfit = calculateExpectedProfit(testPrice);
3239                        
3240                        // Выбираем цену с максимальной прибылью, но не ниже минимально допустимой
3241                        if (testProfit >= minAcceptableProfit && testProfit > maxProfit) {
3242                            maxProfit = testProfit;
3243                            aggressivePrice = testPrice;
3244                        }
3245                    }
3246                    aggressivePrice = Math.round(aggressivePrice);
3247                } else {
3248                    // Если нет данных о расходах, умеренное снижение
3249                    aggressivePrice = Math.round(targetProduct.price * 0.95);
3250                }
3251                
3252                // Оптимальная: цена для максимизации прибыли (выше текущей)
3253                // Ищем оптимум между текущей ценой и +30%
3254                let optimalPrice = targetProduct.price;
3255                if (productCosts) {
3256                    let maxProfit = calculateExpectedProfit(targetProduct.price);
3257                    for (let multiplier = 1.05; multiplier <= 1.30; multiplier += 0.01) {
3258                        const testPrice = targetProduct.price * multiplier;
3259                        const testProfit = calculateExpectedProfit(testPrice);
3260                        if (testProfit > maxProfit) {
3261                            maxProfit = testProfit;
3262                            optimalPrice = testPrice;
3263                        }
3264                    }
3265                    optimalPrice = Math.round(optimalPrice);
3266                } else {
3267                    // Если нет данных о расходах, используем рыночную цену
3268                    optimalPrice = Math.round(Math.max(targetProduct.price * 1.10, marketOptimalPrice));
3269                }
3270                
3271                // Рассчитываем базовые цены (цены поручения)
3272                const currentBasePrice = targetProduct.price / (1 - ozonDiscount / 100);
3273                const marketCaptureBasePrice = marketCapturePrice / (1 - ozonDiscount / 100);
3274                const aggressiveBasePrice = aggressivePrice / (1 - ozonDiscount / 100);
3275                const optimalBasePrice = optimalPrice / (1 - ozonDiscount / 100);
3276                
3277                return {
3278                    marketCapture: marketCapturePrice,
3279                    aggressive: aggressivePrice,
3280                    optimal: optimalPrice,
3281                    currentPrice: targetProduct.price,
3282                    currentBasePrice: Math.round(currentBasePrice),
3283                    marketCaptureBasePrice: Math.round(marketCaptureBasePrice),
3284                    aggressiveBasePrice: Math.round(aggressiveBasePrice),
3285                    optimalBasePrice: Math.round(optimalBasePrice),
3286                    currentPosition: targetProduct.position,
3287                    currentProfit: null,
3288                    rpi: rpi.toFixed(1),
3289                    priceChange: {
3290                        marketCapture: Math.round(((marketCapturePrice - targetProduct.price) / targetProduct.price * 100)),
3291                        aggressive: Math.round(((aggressivePrice - targetProduct.price) / targetProduct.price * 100)),
3292                        optimal: Math.round(((optimalPrice - targetProduct.price) / targetProduct.price * 100))
3293                    },
3294                    forecast: {
3295                        marketCapture: calculateForecast(marketCapturePrice, targetProduct.price, targetProduct.orders, targetProduct.revenue, productCosts),
3296                        aggressive: calculateForecast(aggressivePrice, targetProduct.price, targetProduct.orders, targetProduct.revenue, productCosts),
3297                        optimal: calculateForecast(optimalPrice, targetProduct.price, targetProduct.orders, targetProduct.revenue, productCosts)
3298                    }
3299                };
3300            }
3301
3302            // Общие рекомендации
3303            let weightedSum = 0;
3304            let weightSum = 0;
3305            products.forEach(p => {
3306                const positionBonus = p.position <= 5 ? 2 : 1;
3307                const weight = p.revenue * positionBonus;
3308                weightedSum += p.price * weight;
3309                weightSum += weight;
3310            });
3311            const weightedPrice = weightSum > 0 ? weightedSum / weightSum : 0;
3312            
3313            const productsWithConversion = products.map(p => ({
3314                ...p,
3315                conversion: p.orders / p.price
3316            })).sort((a, b) => b.conversion - a.conversion);
3317            const topConversionPrice = productsWithConversion.length > 0 
3318                ? productsWithConversion.slice(0, 3).reduce((sum, p) => sum + p.price, 0) / 3
3319                : 0;
3320            
3321            const top10Prices = products.slice(0, 10).map(p => p.price).sort((a, b) => a - b);
3322            const medianPrice = top10Prices.length > 0 
3323                ? top10Prices[Math.floor(top10Prices.length / 2)]
3324                : 0;
3325
3326            const baseOptimalPrice = (weightedPrice * 0.4 + topConversionPrice * 0.3 + medianPrice * 0.3);
3327
3328            const recommendedPrices = {
3329                marketCapture: {
3330                    price: Math.round(baseOptimalPrice * 0.70),
3331                    strategy: 'Захват рынка',
3332                    description: 'Оптимальная цена ниже текущей для максимизации прибыли'
3333                },
3334                aggressive: {
3335                    price: Math.round(baseOptimalPrice * 0.85),
3336                    strategy: 'Агрессивная',
3337                    description: 'Низкая цена для максимальных продаж и быстрого роста позиций'
3338                },
3339                optimal: {
3340                    price: Math.round(baseOptimalPrice),
3341                    strategy: 'Оптимальная',
3342                    description: 'Баланс между прибылью и объемом продаж'
3343                }
3344            };
3345
3346            // Раздельные рекомендации для целевых брендов
3347            const glsProducts = products.filter(p => p.brand.includes('GLS Pharmaceuticals'));
3348            const skinphoriaProducts = products.filter(p => p.brand.includes('Skinphoria'));
3349            
3350            const brandRecommendations = {};
3351            
3352            // Рекомендации для GLS Pharmaceuticals
3353            if (glsProducts.length > 0) {
3354                const glsAvgPosition = glsProducts.reduce((sum, p) => sum + p.position, 0) / glsProducts.length;
3355                const glsAvgPrice = glsProducts.reduce((sum, p) => sum + p.price, 0) / glsProducts.length;
3356                
3357                brandRecommendations.gls = {
3358                    brand: 'GLS Pharmaceuticals',
3359                    currentProducts: glsProducts.map(p => {
3360                        const priceRec = calculatePriceForProduct(p, products);
3361                        return {
3362                            sku: p.sku,
3363                            name: p.name,
3364                            position: p.position,
3365                            price: p.price,
3366                            revenue: p.revenue,
3367                            orders: p.orders,
3368                            recommendations: priceRec
3369                        };
3370                    }),
3371                    avgPosition: Math.round(glsAvgPosition),
3372                    avgPrice: Math.round(glsAvgPrice)
3373                };
3374            }
3375            
3376            // Рекомендации для Skinphoria
3377            if (skinphoriaProducts.length > 0) {
3378                const skinphoriaAvgPosition = skinphoriaProducts.reduce((sum, p) => sum + p.position, 0) / skinphoriaProducts.length;
3379                const skinphoriaAvgPrice = skinphoriaProducts.reduce((sum, p) => sum + p.price, 0) / skinphoriaProducts.length;
3380                
3381                brandRecommendations.skinphoria = {
3382                    brand: 'Skinphoria',
3383                    currentProducts: skinphoriaProducts.map(p => {
3384                        const priceRec = calculatePriceForProduct(p, products);
3385                        return {
3386                            sku: p.sku,
3387                            name: p.name,
3388                            position: p.position,
3389                            price: p.price,
3390                            revenue: p.revenue,
3391                            orders: p.orders,
3392                            recommendations: priceRec
3393                        };
3394                    }),
3395                    avgPosition: Math.round(skinphoriaAvgPosition),
3396                    avgPrice: Math.round(skinphoriaAvgPrice)
3397                };
3398            }
3399
3400            console.log('Ozon Product Parser: Analysis completed successfully');
3401            
3402            return {
3403                totalRevenue,
3404                totalOrders,
3405                avgPrice: Math.round(totalRevenue / totalOrders),
3406                topProducts,
3407                priceSegments: segments,
3408                elasticity: elasticity !== null ? {
3409                    value: elasticity.toFixed(2),
3410                    interpretation: elasticityInterpretation
3411                } : null,
3412                recommendedPrices,
3413                brandRecommendations
3414            };
3415        } catch (error) {
3416            console.error('Ozon Product Parser: Error in analyzeProducts:', error);
3417            return null;
3418        }
3419    }
3420
3421    // Показываем результаты
3422    async function showResultsModal() {
3423        console.log('Ozon Product Parser: Opening results modal');
3424        
3425        try {
3426            // Получаем результаты по спискам
3427            const allListResultsJson = await GM.getValue('ozon_parser_list_results', '{}');
3428            const allListResults = JSON.parse(allListResultsJson);
3429            const listNames = Object.keys(allListResults);
3430
3431            if (listNames.length === 0) {
3432                alert('Нет сохраненных результатов. Сначала выполните парсинг.');
3433                return;
3434            }
3435
3436            const modal = document.createElement('div');
3437            modal.className = 'ozon-parser-modal';
3438
3439            const content = document.createElement('div');
3440            content.className = 'ozon-parser-modal-content';
3441            content.style.maxWidth = '95vw';
3442            content.style.width = '95vw';
3443
3444            content.innerHTML = `
3445                <div class="ozon-parser-modal-header">Результаты парсинга</div>
3446                <div class="ozon-parser-modal-body">
3447                    <div style="margin-bottom: 20px;">
3448                        <label style="font-size: 14px; font-weight: 600; margin-bottom: 8px; display: block;">Выберите список:</label>
3449                        <div style="display: flex; gap: 10px; align-items: center;">
3450                            <select class="ozon-parser-search" id="list-selector" style="flex: 1; margin-bottom: 0;">
3451                                ${listNames.map(listName => {
3452        const list = allListResults[listName];
3453        const date = new Date(list.updatedAt).toLocaleDateString('ru-RU');
3454        const queriesCount = Object.keys(list.queries).length;
3455        return `<option value="${listName}">${listName} (${queriesCount} запросов, обновлен: ${date})</option>`;
3456    }).join('')}
3457                            </select>
3458                            <button class="ozon-parser-btn" id="delete-list-btn" style="background: linear-gradient(135deg, #dc3545 0%, #c82333 100%);">Удалить список</button>
3459                        </div>
3460                    </div>
3461                    <div style="margin-bottom: 15px;">
3462                        <label style="font-size: 14px; font-weight: 600; margin-bottom: 8px; display: block;">Сортировка запросов:</label>
3463                        <select class="ozon-parser-search" id="query-sort-selector" style="margin-bottom: 0;">
3464                            <option value="revenue">По нашей выручке (убывание)</option>
3465                            <option value="alphabet">По алфавиту (А-Я)</option>
3466                        </select>
3467                    </div>
3468                    <input type="text" class="ozon-parser-search" id="query-search" placeholder="Поиск по запросам">
3469                    <input type="text" class="ozon-parser-search" id="sku-search" placeholder="Поиск по SKU">
3470                    <div class="ozon-parser-tabs" id="query-tabs"></div>
3471                    <div id="results-container"></div>
3472                </div>
3473                <div class="ozon-parser-modal-footer">
3474                    <button class="ozon-parser-btn" id="close-results-btn">Закрыть</button>
3475                </div>
3476            `;
3477
3478            modal.appendChild(content);
3479            document.body.appendChild(modal);
3480
3481            // Текущий выбранный список
3482            let currentListName = listNames[0];
3483            let currentResults = allListResults[currentListName].queries;
3484            let currentSortMode = 'revenue';
3485
3486            // Функция для сортировки запросов
3487            function sortQueries(queries, results, sortMode) {
3488                if (sortMode === 'alphabet') {
3489                    return queries.sort((a, b) => a.localeCompare(b, 'ru'));
3490                } else {
3491                    // Сортировка по нашей выручке
3492                    return queries.sort((a, b) => {
3493                        const productsA = results[a];
3494                        const productsB = results[b];
3495                        
3496                        // Находим наши товары и их выручку
3497                        const ourProductsA = productsA.filter(p => p.isTargetBrand);
3498                        const ourProductsB = productsB.filter(p => p.isTargetBrand);
3499                        
3500                        const revenueA = ourProductsA.reduce((sum, p) => sum + p.revenue, 0);
3501                        const revenueB = ourProductsB.reduce((sum, p) => sum + p.revenue, 0);
3502                        
3503                        return revenueB - revenueA; // По убыванию
3504                    });
3505                }
3506            }
3507
3508            // Функция для обновления отображения
3509            async function updateDisplay() {
3510                const queries = Object.keys(currentResults);
3511                const sortedQueries = sortQueries(queries, currentResults, currentSortMode);
3512                
3513                // Создаем вкладки
3514                createTabs(sortedQueries, currentResults, content);
3515                
3516                // Показываем результаты первого запроса
3517                if (sortedQueries.length > 0) {
3518                    await displayResults(sortedQueries[0], currentResults[sortedQueries[0]], content);
3519                }
3520            }
3521
3522            // Обработчик выбора списка
3523            const listSelector = content.querySelector('#list-selector');
3524            listSelector.addEventListener('change', () => {
3525                currentListName = listSelector.value;
3526                currentResults = allListResults[currentListName].queries;
3527                updateDisplay();
3528            });
3529
3530            // Обработчик изменения сортировки
3531            const querySortSelector = content.querySelector('#query-sort-selector');
3532            querySortSelector.addEventListener('change', () => {
3533                currentSortMode = querySortSelector.value;
3534                updateDisplay();
3535            });
3536
3537            // Обработчик удаления списка
3538            const deleteListBtn = content.querySelector('#delete-list-btn');
3539            deleteListBtn.addEventListener('click', async () => {
3540                if (!confirm(`Вы уверены, что хотите удалить список "${currentListName}"?`)) {
3541                    return;
3542                }
3543                
3544                // Удаляем список
3545                delete allListResults[currentListName];
3546                await GM.setValue('ozon_parser_list_results', JSON.stringify(allListResults));
3547                
3548                // Также удаляем из сохраненных списков
3549                const savedListsJson = await GM.getValue('ozon_parser_saved_lists', '{}');
3550                const savedLists = JSON.parse(savedListsJson);
3551                delete savedLists[currentListName];
3552                await GM.setValue('ozon_parser_saved_lists', JSON.stringify(savedLists));
3553                
3554                console.log(`Ozon Product Parser: List "${currentListName}" deleted`);
3555                
3556                // Обновляем UI
3557                const remainingLists = Object.keys(allListResults);
3558                if (remainingLists.length === 0) {
3559                    alert('Все списки удалены');
3560                    modal.remove();
3561                    return;
3562                }
3563                
3564                // Переключаемся на первый оставшийся список
3565                currentListName = remainingLists[0];
3566                currentResults = allListResults[currentListName].queries;
3567                
3568                // Обновляем селектор
3569                listSelector.innerHTML = remainingLists.map(listName => {
3570                    const list = allListResults[listName];
3571                    const date = new Date(list.updatedAt).toLocaleDateString('ru-RU');
3572                    const queriesCount = Object.keys(list.queries).length;
3573                    return `<option value="${listName}">${listName} (${queriesCount} запросов, обновлен: ${date})</option>`;
3574                }).join('');
3575                
3576                updateDisplay();
3577            });
3578
3579            // Обработчик закрытия модального окна
3580            const closeBtn = content.querySelector('#close-results-btn');
3581            closeBtn.addEventListener('click', () => {
3582                modal.remove();
3583            });
3584
3585            // Обработчик поиска по запросам
3586            const querySearchInput = content.querySelector('#query-search');
3587            querySearchInput.addEventListener('input', debounce(() => {
3588                const searchValue = querySearchInput.value.trim().toLowerCase();
3589                const queries = Object.keys(currentResults);
3590                const sortedQueries = sortQueries(queries, currentResults, currentSortMode);
3591                filterQueriesByQuery(searchValue, sortedQueries, currentResults, content);
3592            }, 300));
3593
3594            // Обработчик поиска по SKU
3595            const skuSearchInput = content.querySelector('#sku-search');
3596            skuSearchInput.addEventListener('input', debounce(() => {
3597                const searchValue = skuSearchInput.value.trim();
3598                const queries = Object.keys(currentResults);
3599                const sortedQueries = sortQueries(queries, currentResults, currentSortMode);
3600                filterQueriesBySKU(searchValue, sortedQueries, currentResults, content);
3601            }, 300));
3602
3603            // Создаем вкладки для запросов
3604            const initialQueries = Object.keys(currentResults);
3605            const sortedInitialQueries = sortQueries(initialQueries, currentResults, currentSortMode);
3606            createTabs(sortedInitialQueries, currentResults, content);
3607
3608            // Показываем результаты первого запроса
3609            await displayResults(sortedInitialQueries[0], currentResults[sortedInitialQueries[0]], content);
3610
3611            // Закрытие по клику на фон
3612            modal.addEventListener('click', (e) => {
3613                if (e.target === modal) {
3614                    modal.remove();
3615                }
3616            });
3617
3618            console.log('Ozon Product Parser: Results modal shown');
3619        } catch (error) {
3620            console.error('Ozon Product Parser: Error in showResultsModal:', error);
3621            alert('Ошибка при отображении результатов: ' + error.message);
3622        }
3623    }
3624
3625    // Фильтруем запросы по названию запроса
3626    async function filterQueriesByQuery(queryText, allQueries, results, modalContent) {
3627        if (!queryText) {
3628            createTabs(allQueries, results, modalContent);
3629            await displayResults(allQueries[0], results[allQueries[0]], modalContent);
3630            return;
3631        }
3632
3633        const filteredQueries = allQueries.filter(q => 
3634            q.toLowerCase().includes(queryText)
3635        );
3636
3637        if (filteredQueries.length === 0) {
3638            const container = modalContent.querySelector('#results-container');
3639            container.innerHTML = '<p>Запросы не найдены</p>';
3640            const tabsContainer = modalContent.querySelector('#query-tabs');
3641            tabsContainer.innerHTML = '';
3642            return;
3643        }
3644
3645        createTabs(filteredQueries, results, modalContent);
3646        await displayResults(filteredQueries[0], results[filteredQueries[0]], modalContent);
3647    }
3648
3649    // Фильтруем запросы по SKU
3650    async function filterQueriesBySKU(sku, allQueries, results, modalContent) {
3651        if (!sku) {
3652            createTabs(allQueries, results, modalContent);
3653            await displayResults(allQueries[0], results[allQueries[0]], modalContent);
3654            return;
3655        }
3656
3657        const filteredQueries = allQueries.filter(query => {
3658            const products = results[query];
3659            return products.some(product => product.sku.includes(sku));
3660        });
3661
3662        if (filteredQueries.length === 0) {
3663            const container = modalContent.querySelector('#results-container');
3664            container.innerHTML = '<p>Товары с таким SKU не найдены ни в одном запросе</p>';
3665            const tabsContainer = modalContent.querySelector('#query-tabs');
3666            tabsContainer.innerHTML = '';
3667            return;
3668        }
3669
3670        createTabs(filteredQueries, results, modalContent);
3671        await displayResults(filteredQueries[0], results[filteredQueries[0]], modalContent);
3672    }
3673
3674    // Создаем вкладки для запросов
3675    function createTabs(queries, results, modalContent) {
3676        const tabsContainer = modalContent.querySelector('#query-tabs');
3677        tabsContainer.innerHTML = '';
3678        
3679        queries.forEach((query, index) => {
3680            const tab = document.createElement('button');
3681            tab.className = 'ozon-parser-tab' + (index === 0 ? ' active' : '');
3682            
3683            // Получаем позицию по выручке
3684            const products = results[query];
3685            const position = getOurRevenuePosition(query, products);
3686            
3687            // Формируем текст вкладки
3688            if (position !== null) {
3689                tab.textContent = `${query} (${position} место)`;
3690            } else {
3691                tab.textContent = `${query} (нет наших)`;
3692            }
3693            
3694            tab.addEventListener('click', async () => {
3695                tabsContainer.querySelectorAll('.ozon-parser-tab').forEach(t => t.classList.remove('active'));
3696                tab.classList.add('active');
3697                await displayResults(query, results[query], modalContent);
3698            });
3699            tabsContainer.appendChild(tab);
3700        });
3701        
3702        // Функция для получения нашей позиции по выручке в запросе
3703        function getOurRevenuePosition(query, products) {
3704            // Сортируем все товары по выручке
3705            const sortedByRevenue = [...products].sort((a, b) => b.revenue - a.revenue);
3706            
3707            // Находим первый наш товар
3708            const ourProductIndex = sortedByRevenue.findIndex(p => p.isTargetBrand);
3709            
3710            if (ourProductIndex === -1) {
3711                return null; // Наших товаров нет
3712            }
3713            
3714            return ourProductIndex + 1; // Позиция (1-based)
3715        }
3716    }
3717
3718    // Отображаем результаты для конкретного запроса
3719    async function displayResults(query, productsData, modalContent) {
3720        console.log('Ozon Product Parser: Displaying results for query:', query);
3721        
3722        const container = modalContent.querySelector('#results-container');
3723        
3724        try {
3725            let products;
3726            
3727            if (Array.isArray(productsData)) {
3728                products = productsData;
3729            } else {
3730                container.innerHTML = '<p>Запросы не найдены</p>';
3731                return;
3732            }
3733            
3734            if (!products || products.length === 0) {
3735                container.innerHTML = '<p>Нет данных для этого запроса</p>';
3736                return;
3737            }
3738            
3739            // Получаем аналитику
3740            const analytics = await analyzeProducts(products);
3741            
3742            if (!analytics) {
3743                container.innerHTML = '<p>Ошибка при анализе данных</p>';
3744                return;
3745            }
3746            
3747            // Получаем скидку Ozon для расчета базовой цены
3748            const ozonDiscount = await GM.getValue('ozon_parser_discount', 50);
3749            
3750            let tableHTML = `
3751                <table class="ozon-parser-results-table">
3752                    <thead>
3753                        <tr>
3754                            <th>Позиция</th>
3755                            <th>SKU</th>
3756                            <th>Название</th>
3757                            <th>Бренд</th>
3758                            <th>Текущая цена</th>
3759                            <th>Средняя цена</th>
3760                            <th>Выручка</th>
3761                            <th>Заказы</th>
3762                        </tr>
3763                    </thead>
3764                    <tbody>
3765            `;
3766            
3767            products.forEach(product => {
3768                const rowClass = product.isTargetBrand ? ' class="ozon-parser-highlight"' : '';
3769                const skuLink = `https://www.ozon.ru/product/${product.sku}`;
3770                const basePrice = product.price / (1 - ozonDiscount / 100);
3771                const avgPrice = product.orders > 0 ? product.revenue / product.orders : 0;
3772                
3773                tableHTML += `
3774                    <tr${rowClass}>
3775                        <td>${product.position}</td>
3776                        <td><a href="${skuLink}" target="_blank" class="ozon-parser-sku-link">${product.sku}</a></td>
3777                        <td>${product.name || '—'}</td>
3778                        <td>${product.brand}</td>
3779                        <td>
3780                            <div style="font-weight: 600;">${product.price.toLocaleString('ru-RU')} ₽</div>
3781                            <div style="font-size: 10px; color: #999; margin-top: 2px;">База: ${Math.round(basePrice).toLocaleString('ru-RU')} ₽</div>
3782                        </td>
3783                        <td>
3784                            <div style="font-weight: 600;">${Math.round(avgPrice).toLocaleString('ru-RU')} ₽</div>
3785                        </td>
3786                        <td>${product.revenue.toLocaleString('ru-RU')} ₽</td>
3787                        <td>${product.orders.toLocaleString('ru-RU')}</td>
3788                    </tr>
3789                `;
3790            });
3791            
3792            tableHTML += `
3793                    </tbody>
3794                </table>
3795            `;
3796            
3797            // Добавляем аналитику
3798            tableHTML += `
3799                <div class="ozon-parser-analytics">
3800                    <div class="ozon-parser-analytics-header">📊 Аналитика запроса</div>
3801                    
3802                    ${analytics.elasticity ? `
3803                    <div class="ozon-parser-analytics-section">
3804                        <div class="ozon-parser-analytics-card">
3805                            <div class="ozon-parser-analytics-card-title">Эластичность запроса</div>
3806                            <div class="ozon-parser-analytics-card-value">${analytics.elasticity.value}</div>
3807                            <div style="font-size: 12px; color: #666; margin-top: 8px;">
3808                                ${analytics.elasticity.interpretation}
3809                            </div>
3810                        </div>
3811                    </div>
3812                    ` : ''}
3813                    
3814                    <div class="ozon-parser-analytics-section">
3815                        <div class="ozon-parser-analytics-section-title">Рекомендованные цены</div>
3816                        <div class="ozon-parser-analytics-grid">
3817                            <div class="ozon-parser-analytics-card" style="border: 2px solid #6c757d;">
3818                                <div class="ozon-parser-analytics-card-title">⚡ Захват рынка</div>
3819                                <div class="ozon-parser-analytics-card-value" style="color: #6c757d;">${analytics.recommendedPrices.marketCapture.price.toLocaleString('ru-RU')} ₽</div>
3820                                <div style="font-size: 12px; color: #666; margin-top: 8px;">Оптимальная цена ниже текущей для максимизации прибыли</div>
3821                            </div>
3822                            <div class="ozon-parser-analytics-card" style="border: 2px solid #dc3545;">
3823                                <div class="ozon-parser-analytics-card-title">✅ Оптимальная</div>
3824                                <div class="ozon-parser-analytics-card-value" style="color: #dc3545;">${analytics.recommendedPrices.aggressive.price.toLocaleString('ru-RU')} ₽</div>
3825                                <div style="font-size: 12px; color: #666; margin-top: 8px;">Низкая цена для максимальных продаж и быстрого роста позиций</div>
3826                            </div>
3827                            <div class="ozon-parser-analytics-card" style="border: 2px solid #28a745;">
3828                                <div class="ozon-parser-analytics-card-title">🔥 Агрессивная</div>
3829                                <div class="ozon-parser-analytics-card-value" style="color: #28a745;">${analytics.recommendedPrices.optimal.price.toLocaleString('ru-RU')} ₽</div>
3830                                <div style="font-size: 12px; color: #666; margin-top: 8px;">Баланс между прибылью и объемом продаж</div>
3831                            </div>
3832                        </div>
3833                        <div class="ozon-parser-info">
3834                            💡 Расчет учитывает: средневзвешенную цену по выручке (вес 40%), оптимальную конверсию цена/заказы (вес 30%), медианную цену топ-10 (вес 30%). Товары из топ-5 позиций имеют удвоенный вес.
3835                        </div>
3836                    </div>
3837                    
3838                    ${analytics.brandRecommendations && Object.keys(analytics.brandRecommendations).length > 0 ? `
3839                    <div class="ozon-parser-analytics-section">
3840                        <div class="ozon-parser-analytics-section-title">🎯 Рекомендации для ваших брендов</div>
3841                        ${analytics.brandRecommendations.gls ? `
3842                        <div style="background: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; border: 2px solid #ffc107;">
3843                            <div style="font-size: 18px; font-weight: 600; margin-bottom: 15px; color: #333;">
3844                                ${analytics.brandRecommendations.gls.brand}
3845                            </div>
3846                            <div style="margin-bottom: 15px;">
3847                                <div style="font-size: 13px; color: #666; margin-bottom: 5px;">Текущие товары в выдаче: ${analytics.brandRecommendations.gls.currentProducts.length}</div>
3848                                <div style="font-size: 13px; color: #666; margin-bottom: 5px;">Средняя позиция: ${analytics.brandRecommendations.gls.avgPosition}</div>
3849                                <div style="font-size: 13px; color: #666;">Средняя цена: ${analytics.brandRecommendations.gls.avgPrice.toLocaleString('ru-RU')}</div>
3850                            </div>
3851                            <details style="margin-top: 10px;" open>
3852                                <summary style="cursor: pointer; font-size: 13px; color: #005bff; font-weight: 600;">Показать товары</summary>
3853                                <table class="ozon-parser-analytics-table" style="margin-top: 10px;">
3854                                    <thead>
3855                                        <tr>
3856                                            <th>SKU</th>
3857                                            <th>Название</th>
3858                                            <th>Позиция</th>
3859                                            <th>Текущая цена</th>
3860                                            <th>⚡ Захват рынка</th>
3861                                            <th>✅ Оптимальная</th>
3862                                            <th>🔥 Агрессивная</th>
3863                                        </tr>
3864                                    </thead>
3865                                    <tbody>
3866                                        ${analytics.brandRecommendations.gls.currentProducts.map(p => {
3867        const rec = p.recommendations;
3868        if (!rec) return `
3869                                            <tr>
3870                                                <td><a href="https://www.ozon.ru/product/${p.sku}" target="_blank" class="ozon-parser-sku-link">${p.sku}</a></td>
3871                                                <td style="max-width: 200px; white-space: normal;">${p.name || '—'}</td>
3872                                                <td>${p.position}</td>
3873                                                <td>${p.price.toLocaleString('ru-RU')} ₽</td>
3874                                                <td>—</td>
3875                                                <td>—</td>
3876                                                <td>—</td>
3877                                            </tr>
3878                                            `;
3879        return `
3880                                            <tr>
3881                                                <td><a href="https://www.ozon.ru/product/${p.sku}" target="_blank" class="ozon-parser-sku-link">${p.sku}</a></td>
3882                                                <td style="max-width: 200px; white-space: normal;">${p.name || '—'}</td>
3883                                                <td>${p.position}</td>
3884                                                <td>
3885                                                    <div style="font-weight: 600;">${p.price.toLocaleString('ru-RU')}</div>
3886                                                    <div style="font-size: 10px; color: #999; margin-top: 2px;">База: ${rec.currentBasePrice.toLocaleString('ru-RU')}</div>
3887                                                    <div style="font-size: 11px; color: #666; margin-top: 4px;">Выручка: ${p.revenue.toLocaleString('ru-RU')}</div>
3888                                                    <div style="font-size: 11px; color: #666;">Заказы: ${p.orders.toLocaleString('ru-RU')}</div>
3889                                                    ${rec.skuDiscount ? `
3890                                                    <div style="font-size: 11px; color: #ff6b00; font-weight: 600; margin-top: 4px;">
3891                                                        Скидка Ozon: ${rec.skuDiscount}%
3892                                                    </div>
3893                                                    ` : ''}
3894                                                    ${rec.currentProfit !== null ? `
3895                                                    <div style="font-size: 11px; color: #005bff; font-weight: 600; margin-top: 4px;">
3896                                                        Прибыль: ${rec.currentProfit.toFixed(0)}3897                                                    </div>
3898                                                    ` : ''}
3899                                                </td>
3900                                                <td style="background: #e9ecef;">
3901                                                    <div style="font-weight: 600; color: #6c757d;">${rec.marketCapture.toLocaleString('ru-RU')}</div>
3902                                                    <div style="font-size: 10px; color: #999; margin-top: 2px;">База: ${rec.marketCaptureBasePrice.toLocaleString('ru-RU')}</div>
3903                                                    <div style="font-size: 11px; color: ${rec.priceChange.marketCapture < 0 ? '#dc3545' : '#28a745'};">
3904                                                        ${rec.priceChange.marketCapture > 0 ? '+' : ''}${rec.priceChange.marketCapture}%
3905                                                    </div>
3906                                                    ${rec.forecast && rec.forecast.marketCapture ? `
3907                                                    <div style="font-size: 11px; color: ${rec.forecast.marketCapture.revenueChange >= 0 ? '#28a745' : '#dc3545'}; margin-top: 4px;">
3908                                                        Выручка: ${rec.forecast.marketCapture.revenueChange > 0 ? '+' : ''}${rec.forecast.marketCapture.revenueChange}%
3909                                                    </div>
3910                                                    <div style="font-size: 11px; color: ${rec.forecast.marketCapture.ordersChange >= 0 ? '#28a745' : '#dc3545'};">
3911                                                        Заказы: ${rec.forecast.marketCapture.ordersChange > 0 ? '+' : ''}${rec.forecast.marketCapture.ordersChange}%
3912                                                    </div>
3913                                                     ${rec.forecast.marketCapture.profit !== null ? `
3914                                                     <div style="font-size: 11px; color: ${rec.forecast.marketCapture.profitChange >= 0 ? '#28a745' : '#dc3545'}; font-weight: 600; margin-top: 2px;">
3915                                                         Прибыль: ${rec.forecast.marketCapture.profitChange > 0 ? '+' : ''}${rec.forecast.marketCapture.profitChange}%
3916                                                     </div>
3917                                                     <div style="font-size: 10px; color: #666;">
3918                                                         ${rec.forecast.marketCapture.profit.toFixed(0)}3919                                                     </div>
3920                                                     ` : ''}
3921                                                     ` : ''}
3922                                                </td>
3923                                                <td style="background: #d4edda;">
3924                                                    <div style="font-weight: 600; color: #28a745;">${rec.aggressive.toLocaleString('ru-RU')} ₽</div>
3925                                                    <div style="font-size: 10px; color: #999; margin-top: 2px;">База: ${rec.aggressiveBasePrice.toLocaleString('ru-RU')} ₽</div>
3926                                                    <div style="font-size: 11px; color: ${rec.priceChange.aggressive < 0 ? '#dc3545' : '#28a745'};">
3927                                                        ${rec.priceChange.aggressive > 0 ? '+' : ''}${rec.priceChange.aggressive}%
3928                                                    </div>
3929                                                    ${rec.forecast && rec.forecast.aggressive ? `
3930                                                    <div style="font-size: 11px; color: ${rec.forecast.aggressive.revenueChange >= 0 ? '#28a745' : '#dc3545'}; margin-top: 4px;">
3931                                                        Выручка: ${rec.forecast.aggressive.revenueChange > 0 ? '+' : ''}${rec.forecast.aggressive.revenueChange}%
3932                                                    </div>
3933                                                    <div style="font-size: 11px; color: ${rec.forecast.aggressive.ordersChange >= 0 ? '#28a745' : '#dc3545'};">
3934                                                        Заказы: ${rec.forecast.aggressive.ordersChange > 0 ? '+' : ''}${rec.forecast.aggressive.ordersChange}%
3935                                                    </div>
3936                                                     ${rec.forecast.aggressive.profit !== null ? `
3937                                                     <div style="font-size: 11px; color: ${rec.forecast.aggressive.profitChange >= 0 ? '#28a745' : '#dc3545'}; font-weight: 600; margin-top: 2px;">
3938                                                         Прибыль: ${rec.forecast.aggressive.profitChange > 0 ? '+' : ''}${rec.forecast.aggressive.profitChange}%
3939                                                     </div>
3940                                                     <div style="font-size: 10px; color: #666;">
3941                                                         ${rec.forecast.aggressive.profit.toFixed(0)}3942                                                     </div>
3943                                                     ` : ''}
3944                                                     ` : ''}
3945                                                </td>
3946                                                <td style="background: #fff3cd;">
3947                                                    <div style="font-weight: 600; color: #dc3545;">${rec.optimal.toLocaleString('ru-RU')} ₽</div>
3948                                                    <div style="font-size: 10px; color: #999; margin-top: 2px;">База: ${rec.optimalBasePrice.toLocaleString('ru-RU')} ₽</div>
3949                                                    <div style="font-size: 11px; color: ${rec.priceChange.optimal < 0 ? '#dc3545' : '#28a745'};">
3950                                                        ${rec.priceChange.optimal > 0 ? '+' : ''}${rec.priceChange.optimal}%
3951                                                    </div>
3952                                                    ${rec.forecast && rec.forecast.optimal ? `
3953                                                    <div style="font-size: 11px; color: ${rec.forecast.optimal.revenueChange >= 0 ? '#28a745' : '#dc3545'}; margin-top: 4px;">
3954                                                        Выручка: ${rec.forecast.optimal.revenueChange > 0 ? '+' : ''}${rec.forecast.optimal.revenueChange}%
3955                                                    </div>
3956                                                    <div style="font-size: 11px; color: ${rec.forecast.optimal.ordersChange >= 0 ? '#28a745' : '#dc3545'};">
3957                                                        Заказы: ${rec.forecast.optimal.ordersChange > 0 ? '+' : ''}${rec.forecast.optimal.ordersChange}%
3958                                                    </div>
3959                                                     ${rec.forecast.optimal.profit !== null ? `
3960                                                     <div style="font-size: 11px; color: ${rec.forecast.optimal.profitChange >= 0 ? '#28a745' : '#dc3545'}; font-weight: 600; margin-top: 2px;">
3961                                                         Прибыль: ${rec.forecast.optimal.profitChange > 0 ? '+' : ''}${rec.forecast.optimal.profitChange}%
3962                                                     </div>
3963                                                     <div style="font-size: 10px; color: #666;">
3964                                                         ${rec.forecast.optimal.profit.toFixed(0)}3965                                                     </div>
3966                                                     ` : ''}
3967                                                     ` : ''}
3968                                                </td>
3969                                            </tr>
3970                                            `;
3971    }).join('')}
3972                                    </tbody>
3973                                </table>
3974                            </details>
3975                        </div>
3976                        ` : ''}
3977                        ${analytics.brandRecommendations.skinphoria ? `
3978                        <div style="background: white; padding: 20px; border-radius: 8px; border: 2px solid #ffc107;">
3979                            <div style="font-size: 18px; font-weight: 600; margin-bottom: 15px; color: #333;">
3980                                ${analytics.brandRecommendations.skinphoria.brand}
3981                            </div>
3982                            <div style="margin-bottom: 15px;">
3983                                <div style="font-size: 13px; color: #666; margin-bottom: 5px;">Текущие товары в выдаче: ${analytics.brandRecommendations.skinphoria.currentProducts.length}</div>
3984                                <div style="font-size: 13px; color: #666; margin-bottom: 5px;">Средняя позиция: ${analytics.brandRecommendations.skinphoria.avgPosition}</div>
3985                                <div style="font-size: 13px; color: #666;">Средняя цена: ${analytics.brandRecommendations.skinphoria.avgPrice.toLocaleString('ru-RU')}</div>
3986                            </div>
3987                            <details style="margin-top: 10px;" open>
3988                                <summary style="cursor: pointer; font-size: 13px; color: #005bff; font-weight: 600;">Показать товары</summary>
3989                                <table class="ozon-parser-analytics-table" style="margin-top: 10px;">
3990                                    <thead>
3991                                        <tr>
3992                                            <th>SKU</th>
3993                                            <th>Название</th>
3994                                            <th>Позиция</th>
3995                                            <th>Текущая цена</th>
3996                                            <th>⚡ Захват рынка</th>
3997                                            <th>✅ Оптимальная</th>
3998                                            <th>🔥 Агрессивная</th>
3999                                        </tr>
4000                                    </thead>
4001                                    <tbody>
4002                                        ${analytics.brandRecommendations.skinphoria.currentProducts.map(p => {
4003        const rec = p.recommendations;
4004        if (!rec) return `
4005                                            <tr>
4006                                                <td><a href="https://www.ozon.ru/product/${p.sku}" target="_blank" class="ozon-parser-sku-link">${p.sku}</a></td>
4007                                                <td style="max-width: 200px; white-space: normal;">${p.name || '—'}</td>
4008                                                <td>${p.position}</td>
4009                                                <td>${p.price.toLocaleString('ru-RU')} ₽</td>
4010                                                <td>—</td>
4011                                                <td>—</td>
4012                                                <td>—</td>
4013                                            </tr>
4014                                            `;
4015        return `
4016                                            <tr>
4017                                                <td><a href="https://www.ozon.ru/product/${p.sku}" target="_blank" class="ozon-parser-sku-link">${p.sku}</a></td>
4018                                                <td style="max-width: 200px; white-space: normal;">${p.name || '—'}</td>
4019                                                <td>${p.position}</td>
4020                                                <td>
4021                                                    <div style="font-weight: 600;">${p.price.toLocaleString('ru-RU')}</div>
4022                                                    <div style="font-size: 10px; color: #999; margin-top: 2px;">База: ${rec.currentBasePrice.toLocaleString('ru-RU')}</div>
4023                                                    <div style="font-size: 11px; color: #666; margin-top: 4px;">Выручка: ${p.revenue.toLocaleString('ru-RU')}</div>
4024                                                    <div style="font-size: 11px; color: #666;">Заказы: ${p.orders.toLocaleString('ru-RU')}</div>
4025                                                    ${rec.skuDiscount ? `
4026                                                    <div style="font-size: 11px; color: #ff6b00; font-weight: 600; margin-top: 4px;">
4027                                                        Скидка Ozon: ${rec.skuDiscount}%
4028                                                    </div>
4029                                                    ` : ''}
4030                                                    ${rec.currentProfit !== null ? `
4031                                                    <div style="font-size: 11px; color: #005bff; font-weight: 600; margin-top: 4px;">
4032                                                        Прибыль: ${rec.currentProfit.toFixed(0)}4033                                                    </div>
4034                                                    ` : ''}
4035                                                </td>
4036                                                <td style="background: #e9ecef;">
4037                                                    <div style="font-weight: 600; color: #6c757d;">${rec.marketCapture.toLocaleString('ru-RU')}</div>
4038                                                    <div style="font-size: 10px; color: #999; margin-top: 2px;">База: ${rec.marketCaptureBasePrice.toLocaleString('ru-RU')}</div>
4039                                                    <div style="font-size: 11px; color: ${rec.priceChange.marketCapture < 0 ? '#dc3545' : '#28a745'};">
4040                                                        ${rec.priceChange.marketCapture > 0 ? '+' : ''}${rec.priceChange.marketCapture}%
4041                                                    </div>
4042                                                    ${rec.forecast && rec.forecast.marketCapture ? `
4043                                                    <div style="font-size: 11px; color: ${rec.forecast.marketCapture.revenueChange >= 0 ? '#28a745' : '#dc3545'}; margin-top: 4px;">
4044                                                        Выручка: ${rec.forecast.marketCapture.revenueChange > 0 ? '+' : ''}${rec.forecast.marketCapture.revenueChange}%
4045                                                    </div>
4046                                                    <div style="font-size: 11px; color: ${rec.forecast.marketCapture.ordersChange >= 0 ? '#28a745' : '#dc3545'};">
4047                                                        Заказы: ${rec.forecast.marketCapture.ordersChange > 0 ? '+' : ''}${rec.forecast.marketCapture.ordersChange}%
4048                                                    </div>
4049                                                     ${rec.forecast.marketCapture.profit !== null ? `
4050                                                     <div style="font-size: 11px; color: ${rec.forecast.marketCapture.profitChange >= 0 ? '#28a745' : '#dc3545'}; font-weight: 600; margin-top: 2px;">
4051                                                         Прибыль: ${rec.forecast.marketCapture.profitChange > 0 ? '+' : ''}${rec.forecast.marketCapture.profitChange}%
4052                                                     </div>
4053                                                     <div style="font-size: 10px; color: #666;">
4054                                                         ${rec.forecast.marketCapture.profit.toFixed(0)}4055                                                     </div>
4056                                                     ` : ''}
4057                                                     ` : ''}
4058                                                </td>
4059                                                <td style="background: #d4edda;">
4060                                                    <div style="font-weight: 600; color: #28a745;">${rec.aggressive.toLocaleString('ru-RU')} ₽</div>
4061                                                    <div style="font-size: 10px; color: #999; margin-top: 2px;">База: ${rec.aggressiveBasePrice.toLocaleString('ru-RU')} ₽</div>
4062                                                    <div style="font-size: 11px; color: ${rec.priceChange.aggressive < 0 ? '#dc3545' : '#28a745'};">
4063                                                        ${rec.priceChange.aggressive > 0 ? '+' : ''}${rec.priceChange.aggressive}%
4064                                                    </div>
4065                                                    ${rec.forecast && rec.forecast.aggressive ? `
4066                                                    <div style="font-size: 11px; color: ${rec.forecast.aggressive.revenueChange >= 0 ? '#28a745' : '#dc3545'}; margin-top: 4px;">
4067                                                        Выручка: ${rec.forecast.aggressive.revenueChange > 0 ? '+' : ''}${rec.forecast.aggressive.revenueChange}%
4068                                                    </div>
4069                                                    <div style="font-size: 11px; color: ${rec.forecast.aggressive.ordersChange >= 0 ? '#28a745' : '#dc3545'};">
4070                                                        Заказы: ${rec.forecast.aggressive.ordersChange > 0 ? '+' : ''}${rec.forecast.aggressive.ordersChange}%
4071                                                    </div>
4072                                                     ${rec.forecast.aggressive.profit !== null ? `
4073                                                     <div style="font-size: 11px; color: ${rec.forecast.aggressive.profitChange >= 0 ? '#28a745' : '#dc3545'}; font-weight: 600; margin-top: 2px;">
4074                                                         Прибыль: ${rec.forecast.aggressive.profitChange > 0 ? '+' : ''}${rec.forecast.aggressive.profitChange}%
4075                                                     </div>
4076                                                     <div style="font-size: 10px; color: #666;">
4077                                                         ${rec.forecast.aggressive.profit.toFixed(0)}4078                                                     </div>
4079                                                     ` : ''}
4080                                                     ` : ''}
4081                                                </td>
4082                                                <td style="background: #fff3cd;">
4083                                                    <div style="font-weight: 600; color: #dc3545;">${rec.optimal.toLocaleString('ru-RU')} ₽</div>
4084                                                    <div style="font-size: 10px; color: #999; margin-top: 2px;">База: ${rec.optimalBasePrice.toLocaleString('ru-RU')} ₽</div>
4085                                                    <div style="font-size: 11px; color: ${rec.priceChange.optimal < 0 ? '#dc3545' : '#28a745'};">
4086                                                        ${rec.priceChange.optimal > 0 ? '+' : ''}${rec.priceChange.optimal}%
4087                                                    </div>
4088                                                    ${rec.forecast && rec.forecast.optimal ? `
4089                                                    <div style="font-size: 11px; color: ${rec.forecast.optimal.revenueChange >= 0 ? '#28a745' : '#dc3545'}; margin-top: 4px;">
4090                                                        Выручка: ${rec.forecast.optimal.revenueChange > 0 ? '+' : ''}${rec.forecast.optimal.revenueChange}%
4091                                                    </div>
4092                                                    <div style="font-size: 11px; color: ${rec.forecast.optimal.ordersChange >= 0 ? '#28a745' : '#dc3545'};">
4093                                                        Заказы: ${rec.forecast.optimal.ordersChange > 0 ? '+' : ''}${rec.forecast.optimal.ordersChange}%
4094                                                    </div>
4095                                                     ${rec.forecast.optimal.profit !== null ? `
4096                                                     <div style="font-size: 11px; color: ${rec.forecast.optimal.profitChange >= 0 ? '#28a745' : '#dc3545'}; font-weight: 600; margin-top: 2px;">
4097                                                         Прибыль: ${rec.forecast.optimal.profitChange > 0 ? '+' : ''}${rec.forecast.optimal.profitChange}%
4098                                                     </div>
4099                                                     <div style="font-size: 10px; color: #666;">
4100                                                         ${rec.forecast.optimal.profit.toFixed(0)}4101                                                     </div>
4102                                                     ` : ''}
4103                                                     ` : ''}
4104                                                </td>
4105                                            </tr>
4106                                            `;
4107    }).join('')}
4108                                    </tbody>
4109                                </table>
4110                            </details>
4111                        </div>
4112                        ` : ''}
4113                    </div>
4114                    ` : ''}
4115                    
4116                    <div class="ozon-parser-analytics-section">
4117                        <div class="ozon-parser-analytics-section-title">Общая статистика</div>
4118                        <div class="ozon-parser-analytics-grid">
4119                            <div class="ozon-parser-analytics-card">
4120                                <div class="ozon-parser-analytics-card-title">Общая выручка</div>
4121                                <div class="ozon-parser-analytics-card-value">${analytics.totalRevenue.toLocaleString('ru-RU')}</div>
4122                            </div>
4123                            <div class="ozon-parser-analytics-card">
4124                                <div class="ozon-parser-analytics-card-title">Всего заказов</div>
4125                                <div class="ozon-parser-analytics-card-value">${analytics.totalOrders.toLocaleString('ru-RU')}</div>
4126                            </div>
4127                            <div class="ozon-parser-analytics-card">
4128                                <div class="ozon-parser-analytics-card-title">Средний чек</div>
4129                                <div class="ozon-parser-analytics-card-value">${analytics.avgPrice.toLocaleString('ru-RU')}</div>
4130                            </div>
4131                        </div>
4132                    </div>
4133                    
4134                    <div class="ozon-parser-analytics-section">
4135                        <div class="ozon-parser-analytics-section-title">Топ-5 товаров по выручке</div>
4136                        <table class="ozon-parser-analytics-table">
4137                            <thead>
4138                                <tr>
4139                                    <th>Товар</th>
4140                                    <th>Бренд</th>
4141                                    <th>Цена</th>
4142                                    <th>Выручка</th>
4143                                    <th>Доля</th>
4144                                </tr>
4145                            </thead>
4146                            <tbody>
4147            `;
4148            
4149            analytics.topProducts.forEach(product => {
4150                tableHTML += `
4151                    <tr>
4152                        <td style="max-width: 300px; white-space: normal;">${product.name || '—'}</td>
4153                        <td>${product.brand}</td>
4154                        <td>${product.price.toLocaleString('ru-RU')}</td>
4155                        <td>${product.revenue.toLocaleString('ru-RU')}</td>
4156                        <td>${product.revenueShare}%</td>
4157                    </tr>
4158                `;
4159            });
4160            
4161            tableHTML += `
4162                            </tbody>
4163                        </table>
4164                    </div>
4165                    
4166                    <div class="ozon-parser-analytics-section">
4167                        <div class="ozon-parser-analytics-section-title">Ценовые сегменты</div>
4168                        <table class="ozon-parser-analytics-table">
4169                            <thead>
4170                                <tr>
4171                                    <th>Сегмент</th>
4172                                    <th>Диапазон цен</th>
4173                                    <th>Товаров</th>
4174                                    <th>Средняя цена</th>
4175                                    <th>Выручка</th>
4176                                    <th>Доля выручки</th>
4177                                </tr>
4178                            </thead>
4179                            <tbody>
4180            `;
4181            
4182            analytics.priceSegments.forEach(segment => {
4183                tableHTML += `
4184                    <tr>
4185                        <td>${segment.name}</td>
4186                        <td>${segment.priceRange}</td>
4187                        <td>${segment.count}</td>
4188                        <td>${segment.avgPrice.toLocaleString('ru-RU')}</td>
4189                        <td>${segment.revenue.toLocaleString('ru-RU')}</td>
4190                        <td>
4191                            <div style="display: flex; align-items: center; gap: 10px;">
4192                                <div class="ozon-parser-analytics-bar" style="width: ${segment.revenueShare}%; min-width: 20px;"></div>
4193                                <span>${segment.revenueShare}%</span>
4194                            </div>
4195                        </td>
4196                    </tr>
4197                `;
4198            });
4199            
4200            tableHTML += `
4201                            </tbody>
4202                        </table>
4203                    </div>
4204                </div>
4205            `;
4206            
4207            container.innerHTML = tableHTML;
4208            console.log('Ozon Product Parser: Results displayed successfully');
4209        } catch (error) {
4210            console.error('Ozon Product Parser: Error in displayResults:', error);
4211            container.innerHTML = '<p>Ошибка при отображении результатов: ' + error.message + '</p>';
4212        }
4213    }
4214
4215    // Инициализация
4216    function init() {
4217        console.log('Ozon Product Parser: Initializing...');
4218        
4219        if (document.readyState === 'loading') {
4220            document.addEventListener('DOMContentLoaded', () => {
4221                addStyles();
4222                createUI();
4223                continueParsingIfActive();
4224                continueDiscountCalculationIfActive();
4225            });
4226        } else {
4227            addStyles();
4228            createUI();
4229            continueParsingIfActive();
4230            continueDiscountCalculationIfActive();
4231        }
4232    }
4233
4234    init();
4235})();
Ozon Product Parser | Robomonkey