Ozon Product Parser

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

Size

244.5 KB

Version

1.8.58

Created

Mar 30, 2026

Updated

17 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<div style="margin-bottom: 15px; padding: 15px; background: #f8f9fa; border-radius: 8px;">
1807    <label style="font-size: 14px; font-weight: 600; margin-bottom: 8px; display: block;">📎 Загрузить привязки SKU → Запрос:</label>
1808    <div style="display: flex; gap: 10px; align-items: center; margin-bottom: 10px;">
1809        <input type="file" id="sku-query-bindings-file" accept=".csv,.txt" style="flex: 1; padding: 8px; border: 2px solid #e0e0e0; border-radius: 8px;">
1810        <button class="ozon-parser-btn" id="upload-sku-query-btn" style="background: linear-gradient(135deg, #6c5ce7 0%, #a29bfe 100%);">Загрузить</button>
1811    </div>
1812    <details style="margin-top: 10px;">
1813    <summary style="cursor: pointer; font-size: 12px; color: #666;">Формат файла</summary>
1814    <div style="font-size: 12px; color: #666; margin-top: 8px; line-height: 1.6;">
1815        <strong>CSV (2 столбца):</strong> SKU,ключевой запрос<br>
1816        Пример:<br>
1817        320244429,гинкго билоба<br>
1818        320244430,витамин д3<br>
1819        320244431,омега 3<br><br>
1820        <strong>Или TXT:</strong> SKU - ключевой запрос<br>
1821        Пример:<br>
1822        320244429 - гинкго билоба<br><br>
1823        💡 В Excel: столбец A = SKU, столбец B = запрос. Сохраните как CSV (разделители - запятые).
1824    </div>
1825</details>
1826</div>
1827                <textarea class="ozon-parser-textarea" placeholder="Например:&#10;гинкго билоба&#10;аргинин&#10;витамин д"></textarea>
1828                <div style="margin-top: 15px; display: flex; gap: 10px; align-items: center;">
1829                    <input type="text" class="ozon-parser-search" id="list-name-input" placeholder="Название списка (например: БАДы март 2024)" style="flex: 1; margin-bottom: 0;">
1830                    <button class="ozon-parser-btn secondary" id="save-list-btn">Сохранить список</button>
1831                </div>
1832                <div style="margin-top: 15px;">
1833                    <button class="ozon-parser-btn secondary" id="manage-costs-btn" style="width: 100%;">Управление расходами (себестоимость, комиссия, доставка)</button>
1834                </div>
1835                <div class="ozon-parser-progress" style="display: none;">
1836                    <div class="ozon-parser-progress-text">Обработка запросов...</div>
1837                    <div class="ozon-parser-progress-bar">
1838                        <div class="ozon-parser-progress-fill" style="width: 0%"></div>
1839                    </div>
1840                </div>
1841            </div>
1842            <div class="ozon-parser-modal-footer">
1843                <button class="ozon-parser-btn" id="cancel-parse-btn">Отмена</button>
1844                <button class="ozon-parser-btn" id="start-parsing-btn">Начать парсинг</button>
1845            </div>
1846        `;
1847        
1848        modal.appendChild(content);
1849        document.body.appendChild(modal);
1850
1851        // Закрытие по клику на фон
1852        modal.addEventListener('click', (e) => {
1853            if (e.target === modal) {
1854                modal.remove();
1855            }
1856        });
1857        
1858        // Обработчик кнопки отмены
1859        const cancelBtn = content.querySelector('#cancel-parse-btn');
1860        cancelBtn.addEventListener('click', () => {
1861            modal.remove();
1862        });
1863        
1864        // Обработчик кнопки расчета скидки
1865        const calculateDiscountBtn = content.querySelector('#calculate-discount-btn');
1866        calculateDiscountBtn.addEventListener('click', async () => {
1867            calculateDiscountBtn.disabled = true;
1868            calculateDiscountBtn.textContent = 'Расчет...';
1869            
1870            try {
1871                // Сохраняем флаг для автоматического расчета
1872                await GM.setValue('ozon_parser_calculate_discount', 'true');
1873                
1874                // Открываем страницу в новой вкладке
1875                await GM.openInTab('https://seller.ozon.ru/app/prices/control', false);
1876                
1877                // Ждем результата расчета
1878                let attempts = 0;
1879                const maxAttempts = 60; // 60 секунд максимум
1880                
1881                const checkInterval = setInterval(async () => {
1882                    attempts++;
1883                    const calculatedDiscount = await GM.getValue('ozon_parser_calculated_discount', null);
1884                    const calculateFlag = await GM.getValue('ozon_parser_calculate_discount', 'false');
1885                    
1886                    if (calculatedDiscount !== null && calculateFlag === 'false') {
1887                        // Расчет завершен
1888                        clearInterval(checkInterval);
1889                        
1890                        // Вставляем значение в поле
1891                        const discountInput = content.querySelector('#ozon-discount-input');
1892                        discountInput.value = parseFloat(calculatedDiscount).toFixed(1);
1893                        
1894                        // Очищаем временное значение
1895                        await GM.deleteValue('ozon_parser_calculated_discount');
1896                        
1897                        calculateDiscountBtn.disabled = false;
1898                        calculateDiscountBtn.textContent = 'Рассчитать автоматически';
1899                        
1900                        alert(`Скидка Ozon успешно рассчитана: ${parseFloat(calculatedDiscount).toFixed(1)}%`);
1901                    } else if (attempts >= maxAttempts) {
1902                        // Таймаут
1903                        clearInterval(checkInterval);
1904                        calculateDiscountBtn.disabled = false;
1905                        calculateDiscountBtn.textContent = 'Рассчитать автоматически';
1906                        alert('Не удалось рассчитать скидку. Попробуйте еще раз.');
1907                    }
1908                }, 1000);
1909            } catch (error) {
1910                console.error('Ozon Product Parser: Error calculating discount:', error);
1911                calculateDiscountBtn.disabled = false;
1912                calculateDiscountBtn.textContent = 'Рассчитать автоматически';
1913                alert('Ошибка при расчете скидки: ' + error.message);
1914            }
1915        });
1916
1917        // Обработчик кнопки сохранения списка
1918        const saveListBtn = content.querySelector('#save-list-btn');
1919        saveListBtn.addEventListener('click', async () => {
1920            const textarea = content.querySelector('.ozon-parser-textarea');
1921            const listNameInput = content.querySelector('#list-name-input');
1922            const queries = textarea.value.split('\n').filter(q => q.trim());
1923            const listName = listNameInput.value.trim();
1924            
1925            if (queries.length === 0) {
1926                alert('Пожалуйста, введите хотя бы один запрос');
1927                return;
1928            }
1929            
1930            if (!listName) {
1931                alert('Пожалуйста, введите название списка');
1932                return;
1933            }
1934            
1935            // Сохраняем список
1936            const savedListsJson = await GM.getValue('ozon_parser_saved_lists', '{}');
1937            const savedLists = JSON.parse(savedListsJson);
1938            
1939            savedLists[listName] = {
1940                queries: queries,
1941                createdAt: new Date().toISOString(),
1942                updatedAt: new Date().toISOString()
1943            };
1944            
1945            await GM.setValue('ozon_parser_saved_lists', JSON.stringify(savedLists));
1946            
1947            alert(`Список "${listName}" успешно сохранен!`);
1948            console.log(`Ozon Product Parser: List "${listName}" saved with ${queries.length} queries`);
1949        });
1950        
1951        // Обработчик кнопки загрузки списка
1952        const loadListBtn = content.querySelector('#load-list-btn');
1953        loadListBtn.addEventListener('click', async () => {
1954            await showLoadListModal(content);
1955        });
1956        
1957        // Обработчик кнопки управления расходами
1958        const manageCostsBtn = content.querySelector('#manage-costs-btn');
1959        manageCostsBtn.addEventListener('click', async () => {
1960            await showManageCostsModal();
1961        });
1962		// Обработчик загрузки привязок SKU → Запрос
1963const uploadSkuQueryBtn = content.querySelector('#upload-sku-query-btn');
1964uploadSkuQueryBtn.addEventListener('click', async () => {
1965    const fileInput = content.querySelector('#sku-query-bindings-file');
1966    const file = fileInput.files[0];
1967    
1968    if (!file) {
1969        alert('Пожалуйста, выберите файл');
1970        return;
1971    }
1972    
1973    try {
1974        const text = await file.text();
1975        const lines = text.trim().split('\n');
1976        const bindings = {};
1977        const queries = [];
1978        let parsedCount = 0;
1979        
1980        for (const line of lines) {
1981            const trimmedLine = line.trim();
1982            if (!trimmedLine || trimmedLine.toLowerCase().includes('sku')) continue; // Пропускаем заголовок
1983            
1984            let sku = '';
1985            let query = '';
1986            
1987            // Определяем формат: CSV (запятая) или TXT (дефис)
1988            if (trimmedLine.includes(',')) {
1989                // CSV формат: SKU,запрос
1990                const parts = trimmedLine.split(',');
1991                if (parts.length >= 2) {
1992                    sku = parts[0].trim().replace(/^["']|["']$/g, ''); // Убираем кавычки
1993                    query = parts.slice(1).join(',').trim().replace(/^["']|["']$/g, ''); // Остальное - запрос (может содержать запятые)
1994                }
1995            } else if (trimmedLine.includes('-')) {
1996                // TXT формат: SKU - запрос
1997                const parts = trimmedLine.split(/\s*-\s*/);
1998                if (parts.length >= 2) {
1999                    sku = parts[0].trim();
2000                    query = parts.slice(1).join(' - ').trim(); // На случай если в запросе тоже есть дефисы
2001                }
2002            }
2003            
2004            if (sku && query) {
2005                bindings[sku] = query;
2006                if (!queries.includes(query)) {
2007                    queries.push(query);
2008                }
2009                parsedCount++;
2010            }
2011        }
2012        
2013        if (Object.keys(bindings).length === 0) {
2014            alert('Не удалось распознать данные. Проверьте формат:\n\nCSV: SKU,запрос\nTXT: SKU - запрос');
2015            return;
2016        }
2017        
2018        // Сохраняем привязки
2019        const existingBindingsJson = await GM.getValue('ozon_parser_sku_query_bindings', '{}');
2020        const existingBindings = JSON.parse(existingBindingsJson);
2021        const mergedBindings = { ...existingBindings, ...bindings };
2022        await GM.setValue('ozon_parser_sku_query_bindings', JSON.stringify(mergedBindings));
2023        
2024        // Добавляем запросы в textarea (если их там еще нет)
2025        const textarea = content.querySelector('.ozon-parser-textarea');
2026        const existingQueries = textarea.value.split('\n').filter(q => q.trim());
2027        const newQueries = queries.filter(q => !existingQueries.includes(q));
2028        
2029        if (newQueries.length > 0) {
2030            const updatedQueries = [...existingQueries, ...newQueries];
2031            textarea.value = updatedQueries.join('\n');
2032        }
2033        
2034        // Очищаем input
2035        fileInput.value = '';
2036        
2037        // Формируем сообщение о результате
2038        const message = [
2039            `✅ Успешно загружено ${parsedCount} привязок SKU → Запрос`,
2040            `📋 Добавлено ${newQueries.length} новых запросов в список`,
2041            ``,
2042            `📎 Привязанные SKU:`,
2043            ...Object.entries(bindings).slice(0, 10).map(([sku, query]) => `  ${sku}${query}`),
2044            Object.keys(bindings).length > 10 ? `  ... и ещё ${Object.keys(bindings).length - 10}` : ''
2045        ].filter(Boolean).join('\n');
2046        
2047        alert(message);
2048        
2049        console.log('Ozon Product Parser: SKU-Query bindings loaded:', bindings);
2050    } catch (error) {
2051        console.error('Ozon Product Parser: Error loading SKU-Query bindings:', error);
2052        alert('Ошибка при загрузке файла: ' + error.message);
2053    }
2054});
2055        
2056        // Обработчик кнопки парсинга
2057        const startBtn = content.querySelector('#start-parsing-btn');
2058        startBtn.addEventListener('click', async () => {
2059            const textarea = content.querySelector('.ozon-parser-textarea');
2060            const listNameInput = content.querySelector('#list-name-input');
2061            const ozonDiscountInput = content.querySelector('#ozon-discount-input');
2062            const queries = textarea.value.split('\n').filter(q => q.trim());
2063            const listName = listNameInput.value.trim() || 'Без названия';
2064            const ozonDiscount = parseFloat(ozonDiscountInput.value) || 50;
2065            
2066            if (queries.length === 0) {
2067                alert('Пожалуйста, введите хотя бы один запрос');
2068                return;
2069            }
2070            
2071            if (ozonDiscount < 0 || ozonDiscount > 100) {
2072                alert('Скидка Ozon должна быть от 0 до 100%');
2073                return;
2074            }
2075            
2076            // Сохраняем скидку Ozon
2077            await GM.setValue('ozon_parser_discount', ozonDiscount);
2078            
2079            startBtn.disabled = true;
2080            startBtn.textContent = 'Парсинг...';
2081            await startParsing(queries, listName, content);
2082        });
2083        
2084        console.log('Ozon Product Parser: Parse modal shown');
2085    }
2086
2087    // Показываем модальное окно для загрузки сохраненного списка
2088    async function showLoadListModal(parentContent) {
2089        const savedListsJson = await GM.getValue('ozon_parser_saved_lists', '{}');
2090        const savedLists = JSON.parse(savedListsJson);
2091        const listNames = Object.keys(savedLists);
2092        
2093        if (listNames.length === 0) {
2094            alert('Нет сохраненных списков');
2095            return;
2096        }
2097        
2098        const loadModal = document.createElement('div');
2099        loadModal.className = 'ozon-parser-modal';
2100        loadModal.style.zIndex = '10002';
2101        
2102        const loadContent = document.createElement('div');
2103        loadContent.className = 'ozon-parser-modal-content';
2104        loadContent.style.maxWidth = '600px';
2105        
2106        let listsHTML = '<div class="ozon-parser-modal-header">Выберите список</div><div class="ozon-parser-modal-body">';
2107        
2108        listNames.forEach(listName => {
2109            const list = savedLists[listName];
2110            const date = new Date(list.createdAt).toLocaleDateString('ru-RU');
2111            const queriesCount = list.queries.length;
2112            listsHTML += `
2113                <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;" 
2114                     class="saved-list-item" data-list-name="${listName}">
2115                    <div style="flex: 1; cursor: pointer;" class="saved-list-info">
2116                        <div style="font-weight: 600; font-size: 16px; margin-bottom: 5px;">${listName}</div>
2117                        <div style="font-size: 13px; color: #666;">Запросов: ${queriesCount} | Создан: ${date}</div>
2118                    </div>
2119                    <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>
2120                </div>
2121            `;
2122        });
2123        
2124        listsHTML += '</div><div class="ozon-parser-modal-footer"><button class="ozon-parser-btn" id="close-load-modal">Отмена</button></div>';
2125        
2126        loadContent.innerHTML = listsHTML;
2127        loadModal.appendChild(loadContent);
2128        document.body.appendChild(loadModal);
2129        
2130        // Обработчик выбора списка (только для info блока)
2131        loadContent.querySelectorAll('.saved-list-info').forEach(info => {
2132            info.addEventListener('click', () => {
2133                const listItem = info.closest('.saved-list-item');
2134                const listName = listItem.getAttribute('data-list-name');
2135                const list = savedLists[listName];
2136                
2137                // Заполняем textarea
2138                const textarea = parentContent.querySelector('.ozon-parser-textarea');
2139                const listNameInput = parentContent.querySelector('#list-name-input');
2140                textarea.value = list.queries.join('\n');
2141                listNameInput.value = listName;
2142                
2143                loadModal.remove();
2144            });
2145            
2146            // Hover эффект
2147            const listItem = info.closest('.saved-list-item');
2148            info.addEventListener('mouseenter', () => {
2149                listItem.style.borderColor = '#005bff';
2150                listItem.style.background = '#f8f9fa';
2151            });
2152            info.addEventListener('mouseleave', () => {
2153                listItem.style.borderColor = '#e0e0e0';
2154                listItem.style.background = 'white';
2155            });
2156        });
2157        
2158        // Обработчик удаления списка
2159        loadContent.querySelectorAll('[data-delete-list]').forEach(deleteBtn => {
2160            deleteBtn.addEventListener('click', async (e) => {
2161                e.stopPropagation();
2162                const listName = deleteBtn.getAttribute('data-delete-list');
2163                
2164                if (!confirm(`Вы уверены, что хотите удалить список "${listName}"?`)) {
2165                    return;
2166                }
2167                
2168                // Удаляем список из savedLists
2169                delete savedLists[listName];
2170                await GM.setValue('ozon_parser_saved_lists', JSON.stringify(savedLists));
2171                
2172                // Также удаляем результаты парсинга для этого списка
2173                const listResultsJson = await GM.getValue('ozon_parser_list_results', '{}');
2174                const listResults = JSON.parse(listResultsJson);
2175                delete listResults[listName];
2176                await GM.setValue('ozon_parser_list_results', JSON.stringify(listResults));
2177                
2178                console.log(`Ozon Product Parser: List "${listName}" deleted from load modal`);
2179                
2180                // Удаляем элемент из DOM
2181                const listItem = deleteBtn.closest('.saved-list-item');
2182                listItem.remove();
2183                
2184                // Если списков не осталось, закрываем модальное окно
2185                const remainingLists = loadContent.querySelectorAll('.saved-list-item');
2186                if (remainingLists.length === 0) {
2187                    alert('Все списки удалены');
2188                    loadModal.remove();
2189                }
2190            });
2191        });
2192        
2193        // Закрытие
2194        loadContent.querySelector('#close-load-modal').addEventListener('click', () => {
2195            loadModal.remove();
2196        });
2197        
2198        loadModal.addEventListener('click', (e) => {
2199            if (e.target === loadModal) {
2200                loadModal.remove();
2201            }
2202        });
2203    }
2204
2205    // Показываем модальное окно управления расходами
2206    async function showManageCostsModal() {
2207        const costsJson = await GM.getValue('ozon_parser_costs', '{}');
2208        const costs = JSON.parse(costsJson);
2209        
2210        const modal = document.createElement('div');
2211        modal.className = 'ozon-parser-modal';
2212        modal.style.zIndex = '10002';
2213        
2214        const content = document.createElement('div');
2215        content.className = 'ozon-parser-modal-content';
2216        content.style.maxWidth = '800px';
2217        
2218        content.innerHTML = `
2219            <div class="ozon-parser-modal-header">Управление расходами</div>
2220            <div class="ozon-parser-modal-body">
2221                <div class="ozon-parser-info">
2222                    Укажите расходы для ваших товаров (SKU). Эти данные будут использоваться для расчета прибыли и оптимальной цены.
2223                </div>
2224                
2225                <!-- Блок загрузки файла -->
2226                <div style="margin-bottom: 20px; padding: 15px; background: #f8f9fa; border-radius: 8px;">
2227                    <label style="font-size: 14px; font-weight: 600; margin-bottom: 8px; display: block;">Загрузить данные из файла:</label>
2228                    <div style="display: flex; gap: 10px; align-items: center; margin-bottom: 10px;">
2229                        <input type="file" id="cost-file-input" accept=".csv,.json,.txt" style="flex: 1; padding: 8px; border: 2px solid #e0e0e0; border-radius: 8px;">
2230                        <button class="ozon-parser-btn" id="upload-costs-btn">Загрузить</button>
2231                    </div>
2232                    <details style="margin-top: 10px;">
2233                        <summary style="cursor: pointer; font-size: 12px; color: #666;">Формат файлов</summary>
2234                        <div style="font-size: 12px; color: #666; margin-top: 8px; line-height: 1.6;">
2235                            <strong>CSV/TXT:</strong> SKU,Себестоимость,Комиссия,Доставка<br>
2236                            Пример: 320244429,158.4,50,90<br><br>
2237                            <strong>JSON:</strong> {"SKU": {"cost": 158.4, "commission": 0.5, "delivery": 90}}<br>
2238                            Примечание: Комиссия в CSV указывается в процентах (50), в JSON - как десятичная дробь (0.5)
2239                        </div>
2240                    </details>
2241                </div>
2242                
2243                <div style="margin-bottom: 15px;">
2244                    <label style="font-size: 14px; font-weight: 600; margin-bottom: 8px; display: block;">Добавить новый товар:</label>
2245                    <div style="display: grid; grid-template-columns: 2fr 1fr 1fr 1fr auto; gap: 10px; align-items: end;">
2246                        <div>
2247                            <label style="font-size: 12px; color: #666;">SKU</label>
2248                            <input type="text" class="ozon-parser-search" id="new-sku" placeholder="320244429" style="margin-bottom: 0;">
2249                        </div>
2250                        <div>
2251                            <label style="font-size: 12px; color: #666;">Себестоимость (₽)</label>
2252                            <input type="number" class="ozon-parser-search" id="new-cost" placeholder="158.4" step="0.01" style="margin-bottom: 0;">
2253                        </div>
2254                        <div>
2255                            <label style="font-size: 12px; color: #666;">Комиссия (%)</label>
2256                            <input type="number" class="ozon-parser-search" id="new-commission" placeholder="50" step="0.1" style="margin-bottom: 0;">
2257                        </div>
2258                        <div>
2259                            <label style="font-size: 12px; color: #666;">Доставка (₽)</label>
2260                            <input type="number" class="ozon-parser-search" id="new-delivery" placeholder="90" step="0.01" style="margin-bottom: 0;">
2261                        </div>
2262                        <button class="ozon-parser-btn" id="add-cost-btn" style="margin-bottom: 0;">Добавить</button>
2263                    </div>
2264                </div>
2265                <div id="costs-list" style="max-height: 400px; overflow-y: auto;">
2266                    ${Object.keys(costs).length === 0 ? '<p style="text-align: center; color: #999;">Нет добавленных товаров</p>' : ''}
2267                </div>
2268            </div>
2269            <div class="ozon-parser-modal-footer">
2270                <button class="ozon-parser-btn" id="close-costs-modal">Закрыть</button>
2271            </div>
2272        `;
2273        
2274        modal.appendChild(content);
2275        document.body.appendChild(modal);
2276        
2277        // Функция для парсинга CSV
2278        function parseCSV(text) {
2279            const lines = text.trim().split('\n');
2280            const result = {};
2281            
2282            for (let i = 0; i < lines.length; i++) {
2283                const line = lines[i].trim();
2284                if (!line || line.startsWith('SKU')) continue; // Пропускаем заголовок
2285                
2286                const parts = line.split(',').map(p => p.trim());
2287                if (parts.length < 4) continue;
2288                
2289                // Удаляем кавычки из SKU, если они есть
2290                let sku = parts[0].replace(/^["']|["']$/g, '');
2291                const cost = parseFloat(parts[1]);
2292                const commission = parseFloat(parts[2]);
2293                const delivery = parseFloat(parts[3]);
2294                
2295                if (sku && !isNaN(cost) && !isNaN(commission) && !isNaN(delivery)) {
2296                    result[sku] = {
2297                        cost: cost,
2298                        commission: commission / 100, // Конвертируем проценты в десятичную дробь
2299                        delivery: delivery
2300                    };
2301                }
2302            }
2303            
2304            return result;
2305        }
2306        
2307        // Функция для парсинга JS-объекта из txt файла
2308        function parseJSObject(text) {
2309            try {
2310                // Удаляем возможные комментарии и лишние пробелы
2311                let cleanText = text.trim();
2312                
2313                // Если текст не начинается с {, добавляем открывающую скобку
2314                if (!cleanText.startsWith('{')) {
2315                    cleanText = '{' + cleanText;
2316                }
2317                
2318                // Если текст не заканчивается на }, добавляем закрывающую скобку
2319                if (!cleanText.endsWith('}')) {
2320                    // Удаляем последнюю запятую, если она есть
2321                    cleanText = cleanText.replace(/,\s*$/, '');
2322                    cleanText = cleanText + '}';
2323                }
2324                
2325                console.log('Ozon Product Parser: Attempting to parse JS object, length:', cleanText.length);
2326                
2327                // Используем eval для парсинга JS объекта (безопасно, т.к. это локальный файл пользователя)
2328                const result = eval('(' + cleanText + ')');
2329                
2330                // Валидация формата
2331                if (typeof result !== 'object' || result === null) {
2332                    throw new Error('Неверный формат: ожидается объект');
2333                }
2334                
2335                // Проверяем структуру данных
2336                for (const sku in result) {
2337                    const item = result[sku];
2338                    if (typeof item !== 'object' || item === null) {
2339                        throw new Error(`Неверный формат данных для SKU ${sku}. Ожидается объект с полями cost, commission, delivery`);
2340                    }
2341                    if (typeof item.cost !== 'number' || typeof item.commission !== 'number' || typeof item.delivery !== 'number') {
2342                        throw new Error(`Неверный формат данных для SKU ${sku}. Ожидается: { cost: число, commission: число (0-1), delivery: число }. Получено: ${JSON.stringify(item)}`);
2343                    }
2344                }
2345                
2346                console.log('Ozon Product Parser: Successfully parsed JS object with', Object.keys(result).length, 'items');
2347                return result;
2348            } catch (error) {
2349                console.error('Ozon Product Parser: JS object parse error:', error);
2350                throw new Error(`Ошибка парсинга JS-объекта: ${error.message}`);
2351            }
2352        }
2353        
2354        // Обработчик загрузки файла
2355        const uploadBtn = content.querySelector('#upload-costs-btn');
2356        uploadBtn.addEventListener('click', async () => {
2357            const fileInput = content.querySelector('#cost-file-input');
2358            const file = fileInput.files[0];
2359            
2360            if (!file) {
2361                alert('Пожалуйста, выберите файл');
2362                return;
2363            }
2364            
2365            try {
2366                const text = await file.text();
2367                let newCosts = {};
2368                
2369                console.log('Ozon Product Parser: Processing file:', file.name, 'Size:', file.size, 'bytes');
2370                
2371                if (file.name.endsWith('.json')) {
2372                    // Парсим JSON
2373                    try {
2374                        newCosts = JSON.parse(text);
2375                        console.log('Ozon Product Parser: Parsed JSON successfully');
2376                    } catch (jsonError) {
2377                        console.error('Ozon Product Parser: JSON parse error:', jsonError);
2378                        throw new Error(`Ошибка парсинга JSON: ${jsonError.message}. Проверьте, что файл содержит корректный JSON формат.`);
2379                    }
2380                    
2381                    // Валидация JSON формата
2382                    for (const sku in newCosts) {
2383                        const item = newCosts[sku];
2384                        if (typeof item !== 'object' || item === null) {
2385                            throw new Error(`Неверный формат данных для SKU ${sku}. Ожидается объект с полями cost, commission, delivery`);
2386                        }
2387                        if (typeof item.cost !== 'number' || typeof item.commission !== 'number' || typeof item.delivery !== 'number') {
2388                            throw new Error(`Неверный формат данных для SKU ${sku}. Ожидается: {"cost": число, "commission": число, "delivery": число}. Получено: ${JSON.stringify(item)}`);
2389                        }
2390                    }
2391                } else if (file.name.endsWith('.txt')) {
2392                    // Пытаемся определить формат: JS-объект или CSV
2393                    const trimmedText = text.trim();
2394                    console.log('Ozon Product Parser: Processing TXT file, first 100 chars:', trimmedText.substring(0, 100));
2395                    
2396                    if (trimmedText.startsWith('{') || trimmedText.includes('{')) {
2397                        // JS-объект формат
2398                        console.log('Ozon Product Parser: Detected JS object format');
2399                        newCosts = parseJSObject(text);
2400                    } else {
2401                        // CSV формат
2402                        console.log('Ozon Product Parser: Detected CSV format');
2403                        newCosts = parseCSV(text);
2404                    }
2405                } else {
2406                    // CSV формат для других расширений
2407                    console.log('Ozon Product Parser: Processing as CSV format');
2408                    newCosts = parseCSV(text);
2409                }
2410                
2411                console.log('Ozon Product Parser: Extracted', Object.keys(newCosts).length, 'items from file');
2412                
2413                if (Object.keys(newCosts).length === 0) {
2414                    alert('Не удалось извлечь данные из файла. Проверьте формат.\n\nОжидаемые форматы:\n\nCSV: SKU,Себестоимость,Комиссия,Доставка\nПример: 320244429,158.4,50,90\n\nJSON: {"SKU": {"cost": 158.4, "commission": 0.5, "delivery": 90}}');
2415                    return;
2416                }
2417                
2418                // Объединяем с существующими данными
2419                const mergedCosts = { ...costs, ...newCosts };
2420                await GM.setValue('ozon_parser_costs', JSON.stringify(mergedCosts));
2421                
2422                // Обновляем costs переменную
2423                Object.assign(costs, newCosts);
2424                
2425                alert(`Успешно загружено ${Object.keys(newCosts).length} товаров`);
2426                console.log('Ozon Product Parser: Successfully saved costs data');
2427                displayCostsList();
2428                
2429                // Очищаем input
2430                fileInput.value = '';
2431            } catch (error) {
2432                console.error('Ozon Product Parser: Error uploading costs file:', error);
2433                console.error('Ozon Product Parser: Error stack:', error.stack);
2434                console.error('Ozon Product Parser: Error name:', error.name);
2435                console.error('Ozon Product Parser: Error message:', error.message);
2436                alert('Ошибка при загрузке файла:\n\n' + error.message + '\n\nПроверьте формат файла и попробуйте снова.');
2437            }
2438        });
2439        
2440        // Функция для отображения списка расходов
2441        function displayCostsList() {
2442            const costsList = content.querySelector('#costs-list');
2443            
2444            if (Object.keys(costs).length === 0) {
2445                costsList.innerHTML = '<p style="text-align: center; color: #999;">Нет добавленных товаров</p>';
2446                return;
2447            }
2448            
2449            let html = '<div style="margin-bottom: 15px; display: flex; justify-content: space-between; align-items: center;">';
2450            html += '<div style="font-size: 14px; font-weight: 600;">Всего товаров: ' + Object.keys(costs).length + '</div>';
2451            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>';
2452            html += '</div>';
2453            html += '<table class="ozon-parser-results-table"><thead><tr><th>SKU</th><th>Себестоимость</th><th>Комиссия</th><th>Доставка</th><th>Действия</th></tr></thead><tbody>';
2454            
2455            Object.keys(costs).forEach(sku => {
2456                const cost = costs[sku];
2457                html += `
2458                    <tr>
2459                        <td><a href="https://www.ozon.ru/product/${sku}" target="_blank" class="ozon-parser-sku-link">${sku}</a></td>
2460                        <td>${cost.cost.toFixed(2)} ₽</td>
2461                        <td>${(cost.commission * 100).toFixed(2)}%</td>
2462                        <td>${cost.delivery.toFixed(2)} ₽</td>
2463                        <td>
2464                            <button class="ozon-parser-btn" data-edit-sku="${sku}" style="padding: 6px 12px; font-size: 12px; margin-right: 5px;">Изменить</button>
2465                            <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>
2466                        </td>
2467                    </tr>
2468                `;
2469            });
2470            
2471            html += '</tbody></table>';
2472            costsList.innerHTML = html;
2473            
2474            // Обработчик для кнопки удаления всех расходов
2475            const deleteAllBtn = costsList.querySelector('#delete-all-costs-btn');
2476            if (deleteAllBtn) {
2477                deleteAllBtn.addEventListener('click', async () => {
2478                    if (confirm('Вы уверены, что хотите удалить ВСЕ данные о расходах? Это действие нельзя отменить.')) {
2479                        // Очищаем все данные
2480                        Object.keys(costs).forEach(key => delete costs[key]);
2481                        await GM.setValue('ozon_parser_costs', JSON.stringify({}));
2482                        displayCostsList();
2483                        alert('Все данные о расходах удалены');
2484                    }
2485                });
2486            }
2487            
2488            // Обработчики для кнопок удаления
2489            costsList.querySelectorAll('[data-delete-sku]').forEach(btn => {
2490                btn.addEventListener('click', async () => {
2491                    const sku = btn.getAttribute('data-delete-sku');
2492                    if (confirm(`Удалить данные о расходах для SKU ${sku}?`)) {
2493                        delete costs[sku];
2494                        await GM.setValue('ozon_parser_costs', JSON.stringify(costs));
2495                        displayCostsList();
2496                    }
2497                });
2498            });
2499            
2500            // Обработчики для кнопок изменения
2501            costsList.querySelectorAll('[data-edit-sku]').forEach(btn => {
2502                btn.addEventListener('click', () => {
2503                    const sku = btn.getAttribute('data-edit-sku');
2504                    const cost = costs[sku];
2505                    
2506                    content.querySelector('#new-sku').value = sku;
2507                    content.querySelector('#new-cost').value = cost.cost;
2508                    content.querySelector('#new-commission').value = cost.commission * 100;
2509                    content.querySelector('#new-delivery').value = cost.delivery;
2510                    
2511                    content.querySelector('#new-sku').scrollIntoView({ behavior: 'smooth', block: 'center' });
2512                });
2513            });
2514        }
2515        
2516        displayCostsList();
2517        
2518        // Обработчик добавления нового товара
2519        const addBtn = content.querySelector('#add-cost-btn');
2520        addBtn.addEventListener('click', async () => {
2521            const sku = content.querySelector('#new-sku').value.trim();
2522            const cost = parseFloat(content.querySelector('#new-cost').value);
2523            const commission = parseFloat(content.querySelector('#new-commission').value);
2524            const delivery = parseFloat(content.querySelector('#new-delivery').value);
2525            
2526            if (!sku) {
2527                alert('Пожалуйста, введите SKU');
2528                return;
2529            }
2530            
2531            if (isNaN(cost) || cost < 0) {
2532                alert('Пожалуйста, введите корректную себестоимость');
2533                return;
2534            }
2535            
2536            if (isNaN(commission) || commission < 0 || commission > 100) {
2537                alert('Пожалуйста, введите корректную комиссию (0-100%)');
2538                return;
2539            }
2540            
2541            if (isNaN(delivery) || delivery < 0) {
2542                alert('Пожалуйста, введите корректную стоимость доставки');
2543                return;
2544            }
2545            
2546            costs[sku] = {
2547                cost: cost,
2548                commission: commission / 100, // Сохраняем как десятичную дробь
2549                delivery: delivery
2550            };
2551            
2552            await GM.setValue('ozon_parser_costs', JSON.stringify(costs));
2553            
2554            // Очищаем поля
2555            content.querySelector('#new-sku').value = '';
2556            content.querySelector('#new-cost').value = '';
2557            content.querySelector('#new-commission').value = '';
2558            content.querySelector('#new-delivery').value = '';
2559            
2560            displayCostsList();
2561        });
2562        
2563        // Закрытие
2564        content.querySelector('#close-costs-modal').addEventListener('click', () => {
2565            modal.remove();
2566        });
2567        
2568        modal.addEventListener('click', (e) => {
2569            if (e.target === modal) {
2570                modal.remove();
2571            }
2572        });
2573    }
2574
2575    // Извлекаем скидку со страницы управления ценами
2576    async function extractDiscountFromPage() {
2577        console.log('Ozon Product Parser: Extracting discount from prices page');
2578        
2579        // Ждем загрузки таблицы с ценами
2580        await waitForPricesTable();
2581        
2582        // Прокручиваем страницу вниз для загрузки цен (ленивая загрузка)
2583        console.log('Ozon Product Parser: Scrolling to load prices');
2584        for (let i = 0; i < 3; i++) {
2585            window.scrollBy(0, 500);
2586            await new Promise(resolve => setTimeout(resolve, 2000));
2587        }
2588        
2589        try {
2590            // Ищем цены по правильным классам
2591            // Цена продажи (с картой Ozon): index_priceByOzonCardCurrency_3DLKf
2592            // Базовая цена (цена поручения): index_priceAmount_3dfpL
2593            
2594            const salePriceElements = document.querySelectorAll('.index_priceByOzonCardCurrency_3DLKf');
2595            const basePriceElements = document.querySelectorAll('.index_priceAmount_3dfpL');
2596            
2597            console.log(`Ozon Product Parser: Found ${salePriceElements.length} sale prices and ${basePriceElements.length} base prices`);
2598            
2599            if (salePriceElements.length === 0 || basePriceElements.length === 0) {
2600                console.error('Ozon Product Parser: Could not find price elements on the page');
2601                alert('Не удалось найти цены на странице. Убедитесь, что у вас есть товары с ценами.');
2602                await GM.setValue('ozon_parser_calculate_discount', 'false');
2603                return;
2604            }
2605            
2606            // Собираем пары цен (базовая и продажная)
2607            let validDiscounts = [];
2608            
2609            // Ищем строки таблицы с обеими ценами
2610            const rows = document.querySelectorAll('tr');
2611            for (const row of rows) {
2612                const salePrice = row.querySelector('.index_priceByOzonCardCurrency_3DLKf');
2613                const basePrice = row.querySelector('.index_priceAmount_3dfpL');
2614                
2615                if (salePrice && basePrice) {
2616                    const salePriceText = salePrice.textContent.trim();
2617                    const basePriceText = basePrice.textContent.trim();
2618                    
2619                    // Парсим цены (убираем пробелы и символ рубля)
2620                    const salePriceValue = parseFloat(salePriceText.replace(/\s/g, '').replace('₽', '').replace(',', '.'));
2621                    const basePriceValue = parseFloat(basePriceText.replace(/\s/g, '').replace('₽', '').replace(',', '.'));
2622                    
2623                    if (!isNaN(salePriceValue) && !isNaN(basePriceValue) && basePriceValue > 0 && salePriceValue > 0) {
2624                        // Рассчитываем скидку: (базовая цена - цена продажи) / базовая цена * 100
2625                        const discountAmount = basePriceValue - salePriceValue;
2626                        const discountPercent = (discountAmount / basePriceValue) * 100;
2627                        
2628                        if (discountPercent > 0 && discountPercent < 100) {
2629                            validDiscounts.push({
2630                                basePrice: basePriceValue,
2631                                salePrice: salePriceValue,
2632                                discount: discountPercent
2633                            });
2634                            
2635                            console.log(`Ozon Product Parser: Base price: ${basePriceValue}₽, Sale price: ${salePriceValue}₽, Discount: ${discountPercent.toFixed(1)}%`);
2636                        }
2637                    }
2638                }
2639            }
2640            
2641            if (validDiscounts.length === 0) {
2642                console.error('Ozon Product Parser: No valid price pairs found');
2643                alert('Не удалось найти валидные пары цен на странице.');
2644                await GM.setValue('ozon_parser_calculate_discount', 'false');
2645                return;
2646            }
2647            
2648            // Рассчитываем среднюю скидку
2649            const avgDiscount = validDiscounts.reduce((sum, item) => sum + item.discount, 0) / validDiscounts.length;
2650            
2651            console.log(`Ozon Product Parser: Calculated average discount from ${validDiscounts.length} products: ${avgDiscount.toFixed(1)}%`);
2652            
2653            // Сохраняем рассчитанную скидку для передачи в основное окно
2654            await GM.setValue('ozon_parser_calculated_discount', avgDiscount);
2655            
2656            // Сбрасываем флаг расчета
2657            await GM.setValue('ozon_parser_calculate_discount', 'false');
2658            
2659            // Закрываем текущую вкладку
2660            window.close();
2661        } catch (error) {
2662            console.error('Ozon Product Parser: Error extracting discount:', error);
2663            alert('Ошибка при извлечении данных о скидке: ' + error.message);
2664            
2665            // Сбрасываем флаг расчета
2666            await GM.setValue('ozon_parser_calculate_discount', 'false');
2667        }
2668    }
2669
2670    // Ждем появления таблицы с ценами
2671    function waitForPricesTable() {
2672        return new Promise((resolve) => {
2673            const checkTable = () => {
2674                const rows = document.querySelectorAll('tr');
2675                if (rows.length > 0) {
2676                    console.log('Ozon Product Parser: Prices table found');
2677                    // Дополнительная задержка для полной загрузки данных
2678                    setTimeout(resolve, 3000);
2679                } else {
2680                    setTimeout(checkTable, 1000);
2681                }
2682            };
2683            checkTable();
2684        });
2685    }
2686
2687    // Прокручиваем страницу для подгрузки товаров
2688    async function scrollToLoadProducts(targetCount = 20) {
2689        console.log(`Ozon Product Parser: Scrolling to load ${targetCount} products`);
2690        
2691        const table = document.querySelector('#mpstat-ozone-search-result table');
2692        if (!table) {
2693            console.error('Ozon Product Parser: Table not found for scrolling');
2694            return;
2695        }
2696        
2697        let previousRowCount = 0;
2698        let attempts = 0;
2699        const maxAttempts = 10;
2700        
2701        while (attempts < maxAttempts) {
2702            const rows = table.querySelectorAll('tbody tr');
2703            const currentRowCount = rows.length;
2704            
2705            console.log(`Ozon Product Parser: Current row count: ${currentRowCount}, target: ${targetCount}`);
2706            
2707            if (currentRowCount >= targetCount) {
2708                console.log(`Ozon Product Parser: Loaded ${currentRowCount} products`);
2709                break;
2710            }
2711            
2712            // Если количество строк не изменилось, значит больше товаров нет
2713            if (currentRowCount === previousRowCount && attempts > 2) {
2714                console.log(`Ozon Product Parser: No more products to load (${currentRowCount} total)`);
2715                break;
2716            }
2717            
2718            previousRowCount = currentRowCount;
2719            
2720            // Прокручиваем к последней строке таблицы
2721            const lastRow = rows[rows.length - 1];
2722            if (lastRow) {
2723                lastRow.scrollIntoView({ behavior: 'smooth', block: 'end' });
2724            }
2725            
2726            // Также прокручиваем окно вниз
2727            window.scrollBy(0, 500);
2728            
2729            // Ждем подгрузки новых товаров
2730            await new Promise(resolve => setTimeout(resolve, 2000));
2731            attempts++;
2732        }
2733        
2734        console.log(`Ozon Product Parser: Scrolling completed after ${attempts} attempts`);
2735    }
2736
2737    // Начинаем парсинг
2738    async function startParsing(queries, listName, modalContent) {
2739        const progressDiv = modalContent.querySelector('.ozon-parser-progress');
2740        progressDiv.style.display = 'block';
2741        
2742        // Сохраняем список запросов и название списка
2743        await GM.setValue('ozon_parser_queries', JSON.stringify(queries));
2744        await GM.setValue('ozon_parser_current_list_name', listName);
2745        await GM.setValue('ozon_parser_current_index', 0);
2746        await GM.setValue('ozon_parser_active', 'true');
2747        console.log(`Ozon Product Parser: Starting parsing process for list "${listName}"`);
2748        
2749        // Переходим к первому запросу
2750        const firstQuery = queries[0].trim();
2751        const searchUrl = `https://www.ozon.ru/search/?text=${encodeURIComponent(firstQuery)}&from_global=true`;
2752        window.location.href = searchUrl;
2753    }
2754
2755    // Продолжаем парсинг после загрузки страницы
2756    async function continueParsingIfActive() {
2757        const isActive = await GM.getValue('ozon_parser_active', 'false');
2758        if (isActive !== 'true') {
2759            return;
2760        }
2761        
2762        console.log('Ozon Product Parser: Continuing parsing process');
2763        
2764        // Ждем появления таблицы
2765        await waitForTable();
2766        
2767        // Получаем текущее состояние
2768        const queriesJson = await GM.getValue('ozon_parser_queries', '[]');
2769        const queries = JSON.parse(queriesJson);
2770        const currentIndex = await GM.getValue('ozon_parser_current_index', 0);
2771        const currentListName = await GM.getValue('ozon_parser_current_list_name', 'Без названия');
2772        
2773        // Получаем результаты для текущего списка
2774        const allListResultsJson = await GM.getValue('ozon_parser_list_results', '{}');
2775        const allListResults = JSON.parse(allListResultsJson);
2776        
2777        if (!allListResults[currentListName]) {
2778            allListResults[currentListName] = {
2779                queries: {},
2780                createdAt: new Date().toISOString(),
2781                updatedAt: new Date().toISOString()
2782            };
2783        }
2784        
2785        if (currentIndex >= queries.length) {
2786            // Парсинг завершен
2787            await GM.setValue('ozon_parser_active', 'false');
2788            
2789            // Обновляем дату последнего обновления списка
2790            allListResults[currentListName].updatedAt = new Date().toISOString();
2791            await GM.setValue('ozon_parser_list_results', JSON.stringify(allListResults));
2792            
2793            console.log('Ozon Product Parser: Parsing completed');
2794            return;
2795        }
2796        
2797        const currentQuery = queries[currentIndex].trim();
2798        console.log(`Ozon Product Parser: Processing query ${currentIndex + 1}/${queries.length}: "${currentQuery}"`);
2799        
2800        // Парсим данные текущей страницы
2801        const products = await parseProducts();
2802        allListResults[currentListName].queries[currentQuery] = products;
2803        
2804        // Анализируем высокие цены
2805        await analyzeHighPrices(currentQuery, products);
2806        
2807        // Сохраняем результаты
2808        await GM.setValue('ozon_parser_list_results', JSON.stringify(allListResults));
2809        console.log(`Ozon Product Parser: Parsed ${products.length} products for "${currentQuery}" in list "${currentListName}"`);
2810        
2811        // Переходим к следующему запросу
2812        const nextIndex = currentIndex + 1;
2813        await GM.setValue('ozon_parser_current_index', nextIndex);
2814        
2815        if (nextIndex < queries.length) {
2816            // Есть еще запросы - переходим к следующему
2817            const nextQuery = queries[nextIndex].trim();
2818            const searchUrl = `https://www.ozon.ru/search/?text=${encodeURIComponent(nextQuery)}&from_global=true`;
2819            setTimeout(() => {
2820                window.location.href = searchUrl;
2821            }, 2000); // Небольшая задержка между запросами
2822        } else {
2823            // Все запросы обработаны
2824            await GM.setValue('ozon_parser_active', 'false');
2825            
2826            // Обновляем дату последнего обновления списка
2827            allListResults[currentListName].updatedAt = new Date().toISOString();
2828            await GM.setValue('ozon_parser_list_results', JSON.stringify(allListResults));
2829            
2830            console.log('Ozon Product Parser: All queries processed');
2831            
2832            // Показываем результаты
2833            setTimeout(() => {
2834                showResultsModal();
2835            }, 1000);
2836        }
2837    }
2838
2839    // Продолжаем расчет скидки после загрузки страницы
2840    async function continueDiscountCalculationIfActive() {
2841        const shouldCalculate = await GM.getValue('ozon_parser_calculate_discount', 'false');
2842        if (shouldCalculate === 'true') {
2843            await GM.setValue('ozon_parser_calculate_discount', 'false');
2844            console.log('Ozon Product Parser: Continuing discount calculation');
2845            await extractDiscountFromPage();
2846        }
2847    }
2848
2849    // Ждем появления таблицы
2850    function waitForTable() {
2851        return new Promise((resolve) => {
2852            const checkTable = () => {
2853                const table = document.querySelector('#mpstat-ozone-search-result table tbody');
2854                if (table && table.querySelectorAll('tr').length > 0) {
2855                    console.log('Ozon Product Parser: Table found');
2856                    // Дополнительная задержка для полной загрузки данных
2857                    setTimeout(resolve, 5000);
2858                } else {
2859                    setTimeout(checkTable, 1000);
2860                }
2861            };
2862            checkTable();
2863        });
2864    }
2865
2866    // Прокручиваем страницу для подгрузки товаров
2867    async function scrollToLoadProducts(targetCount = 20) {
2868        console.log(`Ozon Product Parser: Scrolling to load ${targetCount} products`);
2869        
2870        const table = document.querySelector('#mpstat-ozone-search-result table');
2871        if (!table) {
2872            console.error('Ozon Product Parser: Table not found for scrolling');
2873            return;
2874        }
2875        
2876        let previousRowCount = 0;
2877        let attempts = 0;
2878        const maxAttempts = 10;
2879        
2880        while (attempts < maxAttempts) {
2881            const rows = table.querySelectorAll('tbody tr');
2882            const currentRowCount = rows.length;
2883            
2884            console.log(`Ozon Product Parser: Current row count: ${currentRowCount}, target: ${targetCount}`);
2885            
2886            if (currentRowCount >= targetCount) {
2887                console.log(`Ozon Product Parser: Loaded ${currentRowCount} products`);
2888                break;
2889            }
2890            
2891            // Если количество строк не изменилось, значит больше товаров нет
2892            if (currentRowCount === previousRowCount && attempts > 2) {
2893                console.log(`Ozon Product Parser: No more products to load (${currentRowCount} total)`);
2894                break;
2895            }
2896            
2897            previousRowCount = currentRowCount;
2898            
2899            // Прокручиваем к последней строке таблицы
2900            const lastRow = rows[rows.length - 1];
2901            if (lastRow) {
2902                lastRow.scrollIntoView({ behavior: 'smooth', block: 'end' });
2903            }
2904            
2905            // Также прокручиваем окно вниз
2906            window.scrollBy(0, 500);
2907            
2908            // Ждем подгрузки новых товаров
2909            await new Promise(resolve => setTimeout(resolve, 2000));
2910            attempts++;
2911        }
2912        
2913        console.log(`Ozon Product Parser: Scrolling completed after ${attempts} attempts`);
2914    }
2915
2916    // Парсим товары из таблицы
2917    async function parseProducts() {
2918        const table = document.querySelector('#mpstat-ozone-search-result table');
2919        if (!table) {
2920            console.error('Ozon Product Parser: Table not found');
2921            return [];
2922        }
2923        
2924        // Прокручиваем страницу для подгрузки товаров
2925        await scrollToLoadProducts(20);
2926        
2927        const rows = table.querySelectorAll('tbody tr');
2928        const products = [];
2929        const maxProducts = Math.min(20, rows.length);
2930        
2931        // Получаем названия товаров из карточек на странице
2932        const productLinks = document.querySelectorAll('a[href*="/product/"]');
2933        const productNames = new Map();
2934        
2935        console.log(`Ozon Product Parser: Found ${productLinks.length} product links`);
2936        
2937        productLinks.forEach(link => {
2938            const href = link.getAttribute('href');
2939            const skuMatch = href.match(/\/product\/[^/]+-(\d+)/);
2940            if (skuMatch) {
2941                const sku = skuMatch[1];
2942                // Ищем название в родительском элементе
2943                const parent = link.closest('.tile-root') || link.closest('[data-index]');
2944                if (parent) {
2945                    const nameElement = parent.querySelector('.tsBody500Medium');
2946                    if (nameElement && nameElement.textContent.trim().length > 10) {
2947                        productNames.set(sku, nameElement.textContent.trim());
2948                    }
2949                }
2950            }
2951        });
2952        
2953        console.log(`Ozon Product Parser: Extracted ${productNames.size} product names`);
2954        
2955        // Функция для извлечения количества единиц из названия
2956        function extractQuantity(name) {
2957            if (!name) return null;
2958            
2959            // Ищем паттерны: "120 капсул", "60 таблеток", "180шт", "90 шт"
2960            const patterns = [
2961                /(\d+)\s*(?:капсул|капс|caps)/i,
2962                /(\d+)\s*(?:таблеток|табл|tablets|tabs)/i,
2963                /(\d+)\s*(?:штук|шт|pcs|pieces)/i,
2964                /(\d+)\s*(?:порций|servings)/i
2965            ];
2966            
2967            for (const pattern of patterns) {
2968                const match = name.match(pattern);
2969                if (match) {
2970                    const quantity = parseInt(match[1]);
2971                    if (quantity > 0 && quantity <= 1000) { // Разумные пределы
2972                        return quantity;
2973                    }
2974                }
2975            }
2976            
2977            return null;
2978        }
2979        
2980        for (let i = 0; i < maxProducts; i++) {
2981            const row = rows[i];
2982            const cells = row.querySelectorAll('td');
2983            if (cells.length < 8) continue;
2984            
2985            const position = cells[0]?.textContent.trim() || '';
2986            const sku = cells[2]?.textContent.trim() || '';
2987            const brand = cells[3]?.textContent.trim() || '';
2988            const priceText = cells[4]?.textContent.trim() || '';
2989            const revenueText = cells[6]?.textContent.trim() || '';
2990            const ordersText = cells[7]?.textContent.trim() || '';
2991            
2992            // Получаем название товара из карточки
2993            const name = productNames.get(sku) || '';
2994            
2995            // Извлекаем количество единиц
2996            const quantity = extractQuantity(name);
2997            
2998            // Проверяем, есть ли товар от GLS или Skinphoria
2999            const isTargetBrand = brand.includes('GLS Pharmaceuticals') || brand.includes('Skinphoria');
3000            
3001            // Парсим числовые значения
3002            const price = parseFloat(priceText.replace(/[^\d]/g, '')) || 0;
3003            const revenue = parseFloat(revenueText.replace(/[^\d]/g, '')) || 0;
3004            const orders = parseInt(ordersText.replace(/[^\d]/g, '')) || 0;
3005            
3006            // Рассчитываем цену за единицу
3007            const pricePerUnit = quantity && price > 0 ? price / quantity : null;
3008            
3009            products.push({
3010                position: parseInt(position) || (i + 1),
3011                sku,
3012                name,
3013                brand,
3014                price,
3015                revenue,
3016                orders,
3017                isTargetBrand,
3018                quantity,
3019                pricePerUnit
3020            });
3021        }
3022        
3023        // Сортируем по убыванию выручки
3024        products.sort((a, b) => b.revenue - a.revenue);
3025        console.log(`Ozon Product Parser: Parsed ${products.length} products, ${products.filter(p => p.isTargetBrand).length} from target brands`);
3026        return products;
3027    }
3028    
3029    // Анализируем высокие цены после парсинга
3030    async function analyzeHighPrices(query, products) {
3031        console.log(`Ozon Product Parser: Analyzing high prices for query "${query}"`);
3032        
3033        // Получаем топ-5 конкурентов по выручке
3034        const competitors = products.filter(p => !p.isTargetBrand && p.price > 0);
3035        const top5Competitors = competitors.slice(0, 5);
3036        
3037        if (top5Competitors.length === 0) {
3038            console.log('Ozon Product Parser: No competitors found for high price analysis');
3039            return;
3040        }
3041        
3042        // Находим минимальную цену среди топ-5 конкурентов
3043        const minCompetitorPrice = Math.min(...top5Competitors.map(p => p.price));
3044        console.log(`Ozon Product Parser: Min competitor price in top-5: ${minCompetitorPrice}`);
3045        
3046        // Проверяем наши товары
3047        const ourProducts = products.filter(p => p.isTargetBrand);
3048        const highPriceProducts = [];
3049        
3050        for (const product of ourProducts) {
3051            if (product.price > 0) {
3052                const priceDiff = ((product.price - minCompetitorPrice) / minCompetitorPrice) * 100;
3053                
3054                if (priceDiff > 10) {
3055                    console.log(`Ozon Product Parser: High price detected - SKU ${product.sku}: ${product.price}₽ vs ${minCompetitorPrice}₽ (+${priceDiff.toFixed(1)}%)`);
3056                    
3057                    highPriceProducts.push({
3058                        sku: product.sku,
3059                        name: product.name,
3060                        ourPrice: product.price,
3061                        competitorPrice: minCompetitorPrice,
3062                        query: query
3063                    });
3064                }
3065            }
3066        }
3067        
3068        if (highPriceProducts.length > 0) {
3069            // Добавляем в общий список высоких цен
3070            const existingDataJson = await GM.getValue('ozon_parser_high_price', '[]');
3071            const existingData = JSON.parse(existingDataJson);
3072            
3073            // Удаляем дубликаты по SKU + query
3074            const existingKeys = new Set(existingData.map(item => `${item.sku}_${item.query}`));
3075            const newProducts = highPriceProducts.filter(item => !existingKeys.has(`${item.sku}_${item.query}`));
3076            
3077            if (newProducts.length > 0) {
3078                const updatedData = [...existingData, ...newProducts];
3079                await GM.setValue('ozon_parser_high_price', JSON.stringify(updatedData));
3080                console.log(`Ozon Product Parser: Added ${newProducts.length} products to high price list`);
3081                
3082                // Обновляем счетчик
3083                await updateHighPriceCounter();
3084            }
3085        }
3086    }
3087
3088    // Анализируем данные для запроса
3089    async function analyzeProducts(products) {
3090        console.log('Ozon Product Parser: Starting analysis for', products.length, 'products');
3091        
3092        if (!products || products.length === 0) {
3093            console.error('Ozon Product Parser: No products to analyze');
3094            return null;
3095        }
3096
3097        try {
3098            // Получаем данные о расходах и скидке Ozon
3099            const costsJson = await GM.getValue('ozon_parser_costs', '{}');
3100            const costs = JSON.parse(costsJson);
3101            const ozonDiscount = await GM.getValue('ozon_parser_discount', 50);
3102            console.log(`Ozon Product Parser: Loaded costs for ${Object.keys(costs).length} SKUs, Ozon discount: ${ozonDiscount}%`);
3103
3104            // Общая выручка и заказы
3105            const totalRevenue = products.reduce((sum, p) => sum + p.revenue, 0);
3106            const totalOrders = products.reduce((sum, p) => sum + p.orders, 0);
3107
3108            // Топ-5 товаров по выручке
3109            const topProducts = products.slice(0, 5).map(p => ({
3110                name: p.name,
3111                brand: p.brand,
3112                price: p.price,
3113                revenue: p.revenue,
3114                orders: p.orders,
3115                position: p.position,
3116                revenueShare: ((p.revenue / totalRevenue) * 100).toFixed(1)
3117            }));
3118
3119            // Ценовые сегменты
3120            const prices = products.map(p => p.price).filter(p => p > 0);
3121            const minPrice = Math.min(...prices);
3122            const maxPrice = Math.max(...prices);
3123            const priceRange = maxPrice - minPrice;
3124            const segmentSize = priceRange / 4;
3125
3126            const priceSegments = [
3127                { min: minPrice, max: minPrice + segmentSize, name: 'Низкий' },
3128                { min: minPrice + segmentSize, max: minPrice + segmentSize * 2, name: 'Средний-' },
3129                { min: minPrice + segmentSize * 2, max: minPrice + segmentSize * 3, name: 'Средний+' },
3130                { min: minPrice + segmentSize * 3, max: maxPrice, name: 'Высокий' }
3131            ];
3132
3133            const segments = priceSegments.map(segment => {
3134                const segmentProducts = products.filter(p => 
3135                    p.price >= segment.min && p.price <= segment.max
3136                );
3137                const segmentRevenue = segmentProducts.reduce((sum, p) => sum + p.revenue, 0);
3138                const segmentOrders = segmentProducts.reduce((sum, p) => sum + p.orders, 0);
3139
3140                return {
3141                    name: segment.name,
3142                    priceRange: `${Math.round(segment.min)} - ${Math.round(segment.max)}`,
3143                    count: segmentProducts.length,
3144                    revenue: segmentRevenue,
3145                    orders: segmentOrders,
3146                    revenueShare: ((segmentRevenue / totalRevenue) * 100).toFixed(1),
3147                    avgPrice: segmentProducts.length > 0 
3148                        ? Math.round(segmentProducts.reduce((sum, p) => sum + p.price, 0) / segmentProducts.length)
3149                        : 0
3150                };
3151            }).filter(s => s.count > 0);
3152
3153            // Расчет эластичности запроса
3154            const competitors = products.filter(p => !p.isTargetBrand);
3155            let elasticity = null;
3156            let elasticityInterpretation = '';
3157            
3158            if (competitors.length >= 5) {
3159                const logPrices = competitors.map(p => Math.log(p.price + 1));
3160                const logOrders = competitors.map(p => Math.log(p.orders + 1));
3161                
3162                function simpleRegression(x, y) {
3163                    const n = x.length;
3164                    const sumX = x.reduce((a, b) => a + b, 0);
3165                    const sumY = y.reduce((a, b) => a + b, 0);
3166                    const sumXY = x.reduce((a, b, i) => a + b * y[i], 0);
3167                    const sumX2 = x.reduce((a, b) => a + b * b, 0);
3168                    const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
3169                    return slope;
3170                }
3171                
3172                const rawElasticity = simpleRegression(logPrices, logOrders);
3173                const priorElasticity = -1.2;
3174                const priorWeight = 0.2;
3175                elasticity = rawElasticity * (1 - priorWeight) + priorElasticity * priorWeight;
3176                
3177                console.log(`Ozon Product Parser: Raw elasticity: ${rawElasticity.toFixed(2)}, Bayesian adjusted: ${elasticity.toFixed(2)}`);
3178                
3179                if (elasticity < -1.5) {
3180                    elasticityInterpretation = 'Высокая эластичность - спрос очень чувствителен к цене. Снижение цены сильно увеличит продажи.';
3181                } else if (elasticity < -0.8) {
3182                    elasticityInterpretation = 'Средняя эластичность - спрос умеренно реагирует на изменение цены.';
3183                } else if (elasticity < 0) {
3184                    elasticityInterpretation = 'Низкая эластичность - спрос слабо зависит от цены. Можно повышать цену.';
3185                } else {
3186                    elasticityInterpretation = 'Аномальная эластичность - возможно недостаточно данных для анализа.';
3187                }
3188            }
3189
3190            // Функция для расчета прогноза
3191            function calculateForecast(newPrice, currentPrice, currentOrders, currentRevenue, productCosts) {
3192                const priceChange = (newPrice - currentPrice) / currentPrice;
3193                const usedElasticity = elasticity !== null ? elasticity : -1.2;
3194                const ordersChange = usedElasticity * priceChange;
3195                
3196                const forecastOrders = Math.round(currentOrders * (1 + ordersChange));
3197                const forecastRevenue = Math.round(forecastOrders * newPrice);
3198                
3199                let forecastProfit = null;
3200                let currentProfit = null;
3201                let profitChange = null;
3202                
3203                if (productCosts) {
3204                    const basePrice = newPrice / (1 - ozonDiscount / 100);
3205                    const sellerRevenue = basePrice * (1 - productCosts.commission) * forecastOrders;
3206                    const expenses = (productCosts.cost + productCosts.delivery) * forecastOrders;
3207                    forecastProfit = sellerRevenue - expenses;
3208                    
3209                    const currentBasePrice = currentPrice / (1 - ozonDiscount / 100);
3210                    const currentSellerRevenue = currentBasePrice * (1 - productCosts.commission) * currentOrders;
3211                    const currentExpenses = (productCosts.cost + productCosts.delivery) * currentOrders;
3212                    currentProfit = currentSellerRevenue - currentExpenses;
3213                    
3214                    profitChange = currentProfit > 0 ? Math.round(((forecastProfit - currentProfit) / currentProfit) * 100) : 0;
3215                }
3216                
3217                return {
3218                    orders: forecastOrders,
3219                    ordersChange: Math.round(ordersChange * 100),
3220                    revenue: forecastRevenue,
3221                    revenueChange: Math.round(((forecastRevenue - currentRevenue) / currentRevenue) * 100),
3222                    profit: forecastProfit,
3223                    profitChange: profitChange
3224                };
3225            }
3226
3227            // Функция для расчета рекомендованной цены для конкретного товара
3228            function calculatePriceForProduct(targetProduct, allProducts) {
3229                console.log(`Calculating price for SKU ${targetProduct.sku}`);
3230                
3231                const productCosts = costs[targetProduct.sku];
3232                
3233                // Находим конкурентов
3234                const positionRange = 5;
3235                const nearbyProducts = allProducts.filter(p => 
3236                    !p.isTargetBrand && 
3237                    Math.abs(p.position - targetProduct.position) <= positionRange &&
3238                    p.price > 0 && p.orders > 0
3239                );
3240                
3241                const referenceProducts = nearbyProducts.length >= 3 
3242                    ? nearbyProducts 
3243                    : allProducts.filter(p => !p.isTargetBrand && p.price > 0 && p.orders > 0).slice(0, 10);
3244                
3245                if (referenceProducts.length === 0) {
3246                    return null;
3247                }
3248                
3249                // Рассчитываем базовую оптимальную цену на основе конкурентов
3250                let weightedSum = 0;
3251                let weightSum = 0;
3252                referenceProducts.forEach(p => {
3253                    const weight = p.revenue;
3254                    weightedSum += p.price * weight;
3255                    weightSum += weight;
3256                });
3257                const weightedPrice = weightSum > 0 ? weightedSum / weightSum : 0;
3258                
3259                const productsWithConversion = referenceProducts.map(p => ({
3260                    ...p,
3261                    conversion: p.orders / p.price
3262                })).sort((a, b) => b.conversion - a.conversion);
3263                const topConversionPrice = productsWithConversion.length > 0 
3264                    ? productsWithConversion.slice(0, 3).reduce((sum, p) => sum + p.price, 0) / 3
3265                    : 0;
3266                
3267                const top10Prices = referenceProducts.slice(0, 10).map(p => p.price).sort((a, b) => a - b);
3268                const medianPrice = top10Prices.length > 0 
3269                    ? top10Prices[Math.floor(top10Prices.length / 2)]
3270                    : 0;
3271
3272                const marketOptimalPrice = (weightedPrice * 0.4 + topConversionPrice * 0.3 + medianPrice * 0.3);
3273                
3274                // Рассчитываем RPI
3275                const competitorPrices = allProducts.filter(p => !p.isTargetBrand && p.price > 0);
3276                let avgCompetitorPrice = 0;
3277                if (competitorPrices.length > 0) {
3278                    let weightedPriceSum = 0;
3279                    let weightSum = 0;
3280                    competitorPrices.forEach(comp => {
3281                        const weight = comp.revenue;
3282                        weightedPriceSum += comp.price * weight;
3283                        weightSum += weight;
3284                    });
3285                    avgCompetitorPrice = weightSum > 0 ? weightedPriceSum / weightSum : targetProduct.price;
3286                }
3287                
3288                const rpi = avgCompetitorPrice > 0 ? (targetProduct.price / avgCompetitorPrice) * 100 : 100;
3289                
3290                // Используем эластичность для оптимизации прибыли
3291                const usedElasticity = elasticity !== null ? elasticity : -1.2;
3292                
3293                // Функция для расчета ожидаемой прибыли при заданной цене
3294                function calculateExpectedProfit(price) {
3295                    if (!productCosts) return null;
3296                    
3297                    const priceChange = (price - targetProduct.price) / targetProduct.price;
3298                    const ordersChange = usedElasticity * priceChange;
3299                    const expectedOrders = Math.max(1, targetProduct.orders * (1 + ordersChange));
3300                    
3301                    const basePrice = price / (1 - ozonDiscount / 100);
3302                    const sellerRevenue = basePrice * (1 - productCosts.commission) * expectedOrders;
3303                    const expenses = (productCosts.cost + productCosts.delivery) * expectedOrders;
3304                    
3305                    return sellerRevenue - expenses;
3306                }
3307                
3308                // Рассчитываем 4 варианта цен с учетом эластичности и максимизации прибыли
3309                // Захват рынка: ищем оптимальную цену ниже текущей для РОСТА прибыли
3310                // Цена ОБЯЗАТЕЛЬНО ниже текущей, но прибыль должна РАСТИ за счет увеличения объема
3311                let marketCapturePrice = targetProduct.price;
3312                if (productCosts) {
3313                    const currentProfit = calculateExpectedProfit(targetProduct.price);
3314                    let bestPrice = targetProduct.price;
3315                    let maxProfit = currentProfit;
3316                    
3317                    // Ищем оптимум между -30% и -1% от текущей цены
3318                    for (let multiplier = 0.70; multiplier < 0.99; multiplier += 0.01) {
3319                        const testPrice = targetProduct.price * multiplier;
3320                        const testProfit = calculateExpectedProfit(testPrice);
3321                        
3322                        // Выбираем цену с максимальной прибылью (больше текущей)
3323                        if (testProfit > maxProfit) {
3324                            maxProfit = testProfit;
3325                            bestPrice = testPrice;
3326                        }
3327                    }
3328                    
3329                    // Если нашли цену с большей прибылью - используем её
3330                    if (maxProfit > currentProfit) {
3331                        marketCapturePrice = Math.round(bestPrice);
3332                    } else {
3333                        // Если не нашли - используем безопасное снижение на 5%
3334                        marketCapturePrice = Math.round(targetProduct.price * 0.95);
3335                    }
3336                } else {
3337                    // Если нет данных о расходах, снижаем на 5%
3338                    marketCapturePrice = Math.round(targetProduct.price * 0.95);
3339                }
3340                
3341                // Агрессивная: ищем цену для максимизации прибыли с небольшим снижением
3342                // Допускаем падение прибыли до -2%, но стремимся к росту
3343                let aggressivePrice = targetProduct.price;
3344                if (productCosts) {
3345                    const currentProfit = calculateExpectedProfit(targetProduct.price);
3346                    let maxProfit = currentProfit;
3347                    const minAcceptableProfit = currentProfit * 0.98; // Допускаем падение до -2%
3348                    
3349                    // Ищем оптимум между -10% и +5% от текущей цены
3350                    for (let multiplier = 0.90; multiplier <= 1.05; multiplier += 0.01) {
3351                        const testPrice = targetProduct.price * multiplier;
3352                        const testProfit = calculateExpectedProfit(testPrice);
3353                        
3354                        // Выбираем цену с максимальной прибылью, но не ниже минимально допустимой
3355                        if (testProfit >= minAcceptableProfit && testProfit > maxProfit) {
3356                            maxProfit = testProfit;
3357                            aggressivePrice = testPrice;
3358                        }
3359                    }
3360                    aggressivePrice = Math.round(aggressivePrice);
3361                } else {
3362                    // Если нет данных о расходах, умеренное снижение
3363                    aggressivePrice = Math.round(targetProduct.price * 0.95);
3364                }
3365                
3366                // Оптимальная: цена для максимизации прибыли (выше текущей)
3367                // Ищем оптимум между текущей ценой и +30%
3368                let optimalPrice = targetProduct.price;
3369                if (productCosts) {
3370                    let maxProfit = calculateExpectedProfit(targetProduct.price);
3371                    for (let multiplier = 1.05; multiplier <= 1.30; multiplier += 0.01) {
3372                        const testPrice = targetProduct.price * multiplier;
3373                        const testProfit = calculateExpectedProfit(testPrice);
3374                        if (testProfit > maxProfit) {
3375                            maxProfit = testProfit;
3376                            optimalPrice = testPrice;
3377                        }
3378                    }
3379                    optimalPrice = Math.round(optimalPrice);
3380                } else {
3381                    // Если нет данных о расходах, используем рыночную цену
3382                    optimalPrice = Math.round(Math.max(targetProduct.price * 1.10, marketOptimalPrice));
3383                }
3384                
3385                // Рассчитываем базовые цены (цены поручения)
3386                const currentBasePrice = targetProduct.price / (1 - ozonDiscount / 100);
3387                const marketCaptureBasePrice = marketCapturePrice / (1 - ozonDiscount / 100);
3388                const aggressiveBasePrice = aggressivePrice / (1 - ozonDiscount / 100);
3389                const optimalBasePrice = optimalPrice / (1 - ozonDiscount / 100);
3390                
3391                return {
3392                    marketCapture: marketCapturePrice,
3393                    aggressive: aggressivePrice,
3394                    optimal: optimalPrice,
3395                    currentPrice: targetProduct.price,
3396                    currentBasePrice: Math.round(currentBasePrice),
3397                    marketCaptureBasePrice: Math.round(marketCaptureBasePrice),
3398                    aggressiveBasePrice: Math.round(aggressiveBasePrice),
3399                    optimalBasePrice: Math.round(optimalBasePrice),
3400                    currentPosition: targetProduct.position,
3401                    currentProfit: null,
3402                    rpi: rpi.toFixed(1),
3403                    priceChange: {
3404                        marketCapture: Math.round(((marketCapturePrice - targetProduct.price) / targetProduct.price * 100)),
3405                        aggressive: Math.round(((aggressivePrice - targetProduct.price) / targetProduct.price * 100)),
3406                        optimal: Math.round(((optimalPrice - targetProduct.price) / targetProduct.price * 100))
3407                    },
3408                    forecast: {
3409                        marketCapture: calculateForecast(marketCapturePrice, targetProduct.price, targetProduct.orders, targetProduct.revenue, productCosts),
3410                        aggressive: calculateForecast(aggressivePrice, targetProduct.price, targetProduct.orders, targetProduct.revenue, productCosts),
3411                        optimal: calculateForecast(optimalPrice, targetProduct.price, targetProduct.orders, targetProduct.revenue, productCosts)
3412                    }
3413                };
3414            }
3415
3416            // Общие рекомендации
3417            let weightedSum = 0;
3418            let weightSum = 0;
3419            products.forEach(p => {
3420                const positionBonus = p.position <= 5 ? 2 : 1;
3421                const weight = p.revenue * positionBonus;
3422                weightedSum += p.price * weight;
3423                weightSum += weight;
3424            });
3425            const weightedPrice = weightSum > 0 ? weightedSum / weightSum : 0;
3426            
3427            const productsWithConversion = products.map(p => ({
3428                ...p,
3429                conversion: p.orders / p.price
3430            })).sort((a, b) => b.conversion - a.conversion);
3431            const topConversionPrice = productsWithConversion.length > 0 
3432                ? productsWithConversion.slice(0, 3).reduce((sum, p) => sum + p.price, 0) / 3
3433                : 0;
3434            
3435            const top10Prices = products.slice(0, 10).map(p => p.price).sort((a, b) => a - b);
3436            const medianPrice = top10Prices.length > 0 
3437                ? top10Prices[Math.floor(top10Prices.length / 2)]
3438                : 0;
3439
3440            const baseOptimalPrice = (weightedPrice * 0.4 + topConversionPrice * 0.3 + medianPrice * 0.3);
3441
3442            const recommendedPrices = {
3443                marketCapture: {
3444                    price: Math.round(baseOptimalPrice * 0.70),
3445                    strategy: 'Захват рынка',
3446                    description: 'Оптимальная цена ниже текущей для максимизации прибыли'
3447                },
3448                aggressive: {
3449                    price: Math.round(baseOptimalPrice * 0.85),
3450                    strategy: 'Агрессивная',
3451                    description: 'Низкая цена для максимальных продаж и быстрого роста позиций'
3452                },
3453                optimal: {
3454                    price: Math.round(baseOptimalPrice),
3455                    strategy: 'Оптимальная',
3456                    description: 'Баланс между прибылью и объемом продаж'
3457                }
3458            };
3459
3460            // Раздельные рекомендации для целевых брендов
3461            const glsProducts = products.filter(p => p.brand.includes('GLS Pharmaceuticals'));
3462            const skinphoriaProducts = products.filter(p => p.brand.includes('Skinphoria'));
3463            
3464            const brandRecommendations = {};
3465            
3466            // Рекомендации для GLS Pharmaceuticals
3467            if (glsProducts.length > 0) {
3468                const glsAvgPosition = glsProducts.reduce((sum, p) => sum + p.position, 0) / glsProducts.length;
3469                const glsAvgPrice = glsProducts.reduce((sum, p) => sum + p.price, 0) / glsProducts.length;
3470                
3471                brandRecommendations.gls = {
3472                    brand: 'GLS Pharmaceuticals',
3473                    currentProducts: glsProducts.map(p => {
3474                        const priceRec = calculatePriceForProduct(p, products);
3475                        return {
3476                            sku: p.sku,
3477                            name: p.name,
3478                            position: p.position,
3479                            price: p.price,
3480                            revenue: p.revenue,
3481                            orders: p.orders,
3482                            recommendations: priceRec
3483                        };
3484                    }),
3485                    avgPosition: Math.round(glsAvgPosition),
3486                    avgPrice: Math.round(glsAvgPrice)
3487                };
3488            }
3489            
3490            // Рекомендации для Skinphoria
3491            if (skinphoriaProducts.length > 0) {
3492                const skinphoriaAvgPosition = skinphoriaProducts.reduce((sum, p) => sum + p.position, 0) / skinphoriaProducts.length;
3493                const skinphoriaAvgPrice = skinphoriaProducts.reduce((sum, p) => sum + p.price, 0) / skinphoriaProducts.length;
3494                
3495                brandRecommendations.skinphoria = {
3496                    brand: 'Skinphoria',
3497                    currentProducts: skinphoriaProducts.map(p => {
3498                        const priceRec = calculatePriceForProduct(p, products);
3499                        return {
3500                            sku: p.sku,
3501                            name: p.name,
3502                            position: p.position,
3503                            price: p.price,
3504                            revenue: p.revenue,
3505                            orders: p.orders,
3506                            recommendations: priceRec
3507                        };
3508                    }),
3509                    avgPosition: Math.round(skinphoriaAvgPosition),
3510                    avgPrice: Math.round(skinphoriaAvgPrice)
3511                };
3512            }
3513
3514            console.log('Ozon Product Parser: Analysis completed successfully');
3515            
3516            return {
3517                totalRevenue,
3518                totalOrders,
3519                avgPrice: Math.round(totalRevenue / totalOrders),
3520                topProducts,
3521                priceSegments: segments,
3522                elasticity: elasticity !== null ? {
3523                    value: elasticity.toFixed(2),
3524                    interpretation: elasticityInterpretation
3525                } : null,
3526                recommendedPrices,
3527                brandRecommendations
3528            };
3529        } catch (error) {
3530            console.error('Ozon Product Parser: Error in analyzeProducts:', error);
3531            return null;
3532        }
3533    }
3534
3535    // Показываем результаты
3536    async function showResultsModal() {
3537        console.log('Ozon Product Parser: Opening results modal');
3538        
3539        try {
3540            // Получаем результаты по спискам
3541            const allListResultsJson = await GM.getValue('ozon_parser_list_results', '{}');
3542            const allListResults = JSON.parse(allListResultsJson);
3543            const listNames = Object.keys(allListResults);
3544
3545            if (listNames.length === 0) {
3546                alert('Нет сохраненных результатов. Сначала выполните парсинг.');
3547                return;
3548            }
3549
3550            const modal = document.createElement('div');
3551            modal.className = 'ozon-parser-modal';
3552
3553            const content = document.createElement('div');
3554            content.className = 'ozon-parser-modal-content';
3555            content.style.maxWidth = '95vw';
3556            content.style.width = '95vw';
3557
3558            content.innerHTML = `
3559                <div class="ozon-parser-modal-header">Результаты парсинга</div>
3560                <div class="ozon-parser-modal-body">
3561                    <div style="margin-bottom: 20px;">
3562                        <label style="font-size: 14px; font-weight: 600; margin-bottom: 8px; display: block;">Выберите список:</label>
3563                        <div style="display: flex; gap: 10px; align-items: center;">
3564                            <select class="ozon-parser-search" id="list-selector" style="flex: 1; margin-bottom: 0;">
3565                                ${listNames.map(listName => {
3566        const list = allListResults[listName];
3567        const date = new Date(list.updatedAt).toLocaleDateString('ru-RU');
3568        const queriesCount = Object.keys(list.queries).length;
3569        return `<option value="${listName}">${listName} (${queriesCount} запросов, обновлен: ${date})</option>`;
3570    }).join('')}
3571                            </select>
3572                            <button class="ozon-parser-btn" id="delete-list-btn" style="background: linear-gradient(135deg, #dc3545 0%, #c82333 100%);">Удалить список</button>
3573                        </div>
3574                    </div>
3575                    <div style="margin-bottom: 15px;">
3576                        <label style="font-size: 14px; font-weight: 600; margin-bottom: 8px; display: block;">Сортировка запросов:</label>
3577                        <select class="ozon-parser-search" id="query-sort-selector" style="margin-bottom: 0;">
3578                            <option value="revenue">По нашей выручке (убывание)</option>
3579                            <option value="alphabet">По алфавиту (А-Я)</option>
3580                        </select>
3581                    </div>
3582                    <input type="text" class="ozon-parser-search" id="query-search" placeholder="Поиск по запросам">
3583                    <input type="text" class="ozon-parser-search" id="sku-search" placeholder="Поиск по SKU">
3584                    <div class="ozon-parser-tabs" id="query-tabs"></div>
3585                    <div id="results-container"></div>
3586                </div>
3587                <div class="ozon-parser-modal-footer">
3588                    <button class="ozon-parser-btn" id="close-results-btn">Закрыть</button>
3589                </div>
3590            `;
3591
3592            modal.appendChild(content);
3593            document.body.appendChild(modal);
3594
3595            // Текущий выбранный список
3596            let currentListName = listNames[0];
3597            let currentResults = allListResults[currentListName].queries;
3598            let currentSortMode = 'revenue';
3599
3600            // Функция для сортировки запросов
3601            function sortQueries(queries, results, sortMode) {
3602                if (sortMode === 'alphabet') {
3603                    return queries.sort((a, b) => a.localeCompare(b, 'ru'));
3604                } else {
3605                    // Сортировка по нашей выручке
3606                    return queries.sort((a, b) => {
3607                        const productsA = results[a];
3608                        const productsB = results[b];
3609                        
3610                        // Находим наши товары и их выручку
3611                        const ourProductsA = productsA.filter(p => p.isTargetBrand);
3612                        const ourProductsB = productsB.filter(p => p.isTargetBrand);
3613                        
3614                        const revenueA = ourProductsA.reduce((sum, p) => sum + p.revenue, 0);
3615                        const revenueB = ourProductsB.reduce((sum, p) => sum + p.revenue, 0);
3616                        
3617                        return revenueB - revenueA; // По убыванию
3618                    });
3619                }
3620            }
3621
3622            // Функция для обновления отображения
3623            async function updateDisplay() {
3624                const queries = Object.keys(currentResults);
3625                const sortedQueries = sortQueries(queries, currentResults, currentSortMode);
3626                
3627                // Создаем вкладки
3628                createTabs(sortedQueries, currentResults, content);
3629                
3630                // Показываем результаты первого запроса
3631                if (sortedQueries.length > 0) {
3632                    await displayResults(sortedQueries[0], currentResults[sortedQueries[0]], content);
3633                }
3634            }
3635
3636            // Обработчик выбора списка
3637            const listSelector = content.querySelector('#list-selector');
3638            listSelector.addEventListener('change', () => {
3639                currentListName = listSelector.value;
3640                currentResults = allListResults[currentListName].queries;
3641                updateDisplay();
3642            });
3643
3644            // Обработчик изменения сортировки
3645            const querySortSelector = content.querySelector('#query-sort-selector');
3646            querySortSelector.addEventListener('change', () => {
3647                currentSortMode = querySortSelector.value;
3648                updateDisplay();
3649            });
3650
3651            // Обработчик удаления списка
3652            const deleteListBtn = content.querySelector('#delete-list-btn');
3653            deleteListBtn.addEventListener('click', async () => {
3654                if (!confirm(`Вы уверены, что хотите удалить список "${currentListName}"?`)) {
3655                    return;
3656                }
3657                
3658                // Удаляем список
3659                delete allListResults[currentListName];
3660                await GM.setValue('ozon_parser_list_results', JSON.stringify(allListResults));
3661                
3662                // Также удаляем из сохраненных списков
3663                const savedListsJson = await GM.getValue('ozon_parser_saved_lists', '{}');
3664                const savedLists = JSON.parse(savedListsJson);
3665                delete savedLists[currentListName];
3666                await GM.setValue('ozon_parser_saved_lists', JSON.stringify(savedLists));
3667                
3668                console.log(`Ozon Product Parser: List "${currentListName}" deleted`);
3669                
3670                // Обновляем UI
3671                const remainingLists = Object.keys(allListResults);
3672                if (remainingLists.length === 0) {
3673                    alert('Все списки удалены');
3674                    modal.remove();
3675                    return;
3676                }
3677                
3678                // Переключаемся на первый оставшийся список
3679                currentListName = remainingLists[0];
3680                currentResults = allListResults[currentListName].queries;
3681                
3682                // Обновляем селектор
3683                listSelector.innerHTML = remainingLists.map(listName => {
3684                    const list = allListResults[listName];
3685                    const date = new Date(list.updatedAt).toLocaleDateString('ru-RU');
3686                    const queriesCount = Object.keys(list.queries).length;
3687                    return `<option value="${listName}">${listName} (${queriesCount} запросов, обновлен: ${date})</option>`;
3688                }).join('');
3689                
3690                updateDisplay();
3691            });
3692
3693            // Обработчик закрытия модального окна
3694            const closeBtn = content.querySelector('#close-results-btn');
3695            closeBtn.addEventListener('click', () => {
3696                modal.remove();
3697            });
3698
3699            // Обработчик поиска по запросам
3700            const querySearchInput = content.querySelector('#query-search');
3701            querySearchInput.addEventListener('input', debounce(() => {
3702                const searchValue = querySearchInput.value.trim().toLowerCase();
3703                const queries = Object.keys(currentResults);
3704                const sortedQueries = sortQueries(queries, currentResults, currentSortMode);
3705                filterQueriesByQuery(searchValue, sortedQueries, currentResults, content);
3706            }, 300));
3707
3708            // Обработчик поиска по SKU
3709            const skuSearchInput = content.querySelector('#sku-search');
3710            skuSearchInput.addEventListener('input', debounce(() => {
3711                const searchValue = skuSearchInput.value.trim();
3712                const queries = Object.keys(currentResults);
3713                const sortedQueries = sortQueries(queries, currentResults, currentSortMode);
3714                filterQueriesBySKU(searchValue, sortedQueries, currentResults, content);
3715            }, 300));
3716
3717            // Создаем вкладки для запросов
3718            const initialQueries = Object.keys(currentResults);
3719            const sortedInitialQueries = sortQueries(initialQueries, currentResults, currentSortMode);
3720            createTabs(sortedInitialQueries, currentResults, content);
3721
3722            // Показываем результаты первого запроса
3723            await displayResults(sortedInitialQueries[0], currentResults[sortedInitialQueries[0]], content);
3724
3725            // Закрытие по клику на фон
3726            modal.addEventListener('click', (e) => {
3727                if (e.target === modal) {
3728                    modal.remove();
3729                }
3730            });
3731
3732            console.log('Ozon Product Parser: Results modal shown');
3733        } catch (error) {
3734            console.error('Ozon Product Parser: Error in showResultsModal:', error);
3735            alert('Ошибка при отображении результатов: ' + error.message);
3736        }
3737    }
3738
3739    // Фильтруем запросы по названию запроса
3740    async function filterQueriesByQuery(queryText, allQueries, results, modalContent) {
3741        if (!queryText) {
3742            createTabs(allQueries, results, modalContent);
3743            await displayResults(allQueries[0], results[allQueries[0]], modalContent);
3744            return;
3745        }
3746
3747        const filteredQueries = allQueries.filter(q => 
3748            q.toLowerCase().includes(queryText)
3749        );
3750
3751        if (filteredQueries.length === 0) {
3752            const container = modalContent.querySelector('#results-container');
3753            container.innerHTML = '<p>Запросы не найдены</p>';
3754            const tabsContainer = modalContent.querySelector('#query-tabs');
3755            tabsContainer.innerHTML = '';
3756            return;
3757        }
3758
3759        createTabs(filteredQueries, results, modalContent);
3760        await displayResults(filteredQueries[0], results[filteredQueries[0]], modalContent);
3761    }
3762
3763    // Фильтруем запросы по SKU
3764    async function filterQueriesBySKU(sku, allQueries, results, modalContent) {
3765        if (!sku) {
3766            createTabs(allQueries, results, modalContent);
3767            await displayResults(allQueries[0], results[allQueries[0]], modalContent);
3768            return;
3769        }
3770
3771        const filteredQueries = allQueries.filter(query => {
3772            const products = results[query];
3773            return products.some(product => product.sku.includes(sku));
3774        });
3775
3776        if (filteredQueries.length === 0) {
3777            const container = modalContent.querySelector('#results-container');
3778            container.innerHTML = '<p>Товары с таким SKU не найдены ни в одном запросе</p>';
3779            const tabsContainer = modalContent.querySelector('#query-tabs');
3780            tabsContainer.innerHTML = '';
3781            return;
3782        }
3783
3784        createTabs(filteredQueries, results, modalContent);
3785        await displayResults(filteredQueries[0], results[filteredQueries[0]], modalContent);
3786    }
3787
3788    // Создаем вкладки для запросов
3789    function createTabs(queries, results, modalContent) {
3790        const tabsContainer = modalContent.querySelector('#query-tabs');
3791        tabsContainer.innerHTML = '';
3792        
3793        queries.forEach((query, index) => {
3794            const tab = document.createElement('button');
3795            tab.className = 'ozon-parser-tab' + (index === 0 ? ' active' : '');
3796            
3797            // Получаем позицию по выручке
3798            const products = results[query];
3799            const position = getOurRevenuePosition(query, products);
3800            
3801            // Формируем текст вкладки
3802            if (position !== null) {
3803                tab.textContent = `${query} (${position} место)`;
3804            } else {
3805                tab.textContent = `${query} (нет наших)`;
3806            }
3807            
3808            tab.addEventListener('click', async () => {
3809                tabsContainer.querySelectorAll('.ozon-parser-tab').forEach(t => t.classList.remove('active'));
3810                tab.classList.add('active');
3811                await displayResults(query, results[query], modalContent);
3812            });
3813            tabsContainer.appendChild(tab);
3814        });
3815        
3816        // Функция для получения нашей позиции по выручке в запросе
3817        function getOurRevenuePosition(query, products) {
3818            // Сортируем все товары по выручке
3819            const sortedByRevenue = [...products].sort((a, b) => b.revenue - a.revenue);
3820            
3821            // Находим первый наш товар
3822            const ourProductIndex = sortedByRevenue.findIndex(p => p.isTargetBrand);
3823            
3824            if (ourProductIndex === -1) {
3825                return null; // Наших товаров нет
3826            }
3827            
3828            return ourProductIndex + 1; // Позиция (1-based)
3829        }
3830    }
3831
3832    // Отображаем результаты для конкретного запроса
3833    async function displayResults(query, productsData, modalContent) {
3834        console.log('Ozon Product Parser: Displaying results for query:', query);
3835        
3836        const container = modalContent.querySelector('#results-container');
3837        
3838        try {
3839            let products;
3840            
3841            if (Array.isArray(productsData)) {
3842                products = productsData;
3843            } else {
3844                container.innerHTML = '<p>Запросы не найдены</p>';
3845                return;
3846            }
3847            
3848            if (!products || products.length === 0) {
3849                container.innerHTML = '<p>Нет данных для этого запроса</p>';
3850                return;
3851            }
3852            
3853            // Получаем аналитику
3854            const analytics = await analyzeProducts(products);
3855            
3856            if (!analytics) {
3857                container.innerHTML = '<p>Ошибка при анализе данных</p>';
3858                return;
3859            }
3860            
3861            // Получаем скидку Ozon для расчета базовой цены
3862            const ozonDiscount = await GM.getValue('ozon_parser_discount', 50);
3863            
3864            let tableHTML = `
3865                <table class="ozon-parser-results-table">
3866                    <thead>
3867                        <tr>
3868                            <th>Позиция</th>
3869                            <th>SKU</th>
3870                            <th>Название</th>
3871                            <th>Бренд</th>
3872                            <th>Текущая цена</th>
3873                            <th>Средняя цена</th>
3874                            <th>Выручка</th>
3875                            <th>Заказы</th>
3876                        </tr>
3877                    </thead>
3878                    <tbody>
3879            `;
3880            
3881            products.forEach(product => {
3882                const rowClass = product.isTargetBrand ? ' class="ozon-parser-highlight"' : '';
3883                const skuLink = `https://www.ozon.ru/product/${product.sku}`;
3884                const basePrice = product.price / (1 - ozonDiscount / 100);
3885                const avgPrice = product.orders > 0 ? product.revenue / product.orders : 0;
3886                
3887                tableHTML += `
3888                    <tr${rowClass}>
3889                        <td>${product.position}</td>
3890                        <td><a href="${skuLink}" target="_blank" class="ozon-parser-sku-link">${product.sku}</a></td>
3891                        <td>${product.name || '—'}</td>
3892                        <td>${product.brand}</td>
3893                        <td>
3894                            <div style="font-weight: 600;">${product.price.toLocaleString('ru-RU')} ₽</div>
3895                            <div style="font-size: 10px; color: #999; margin-top: 2px;">База: ${Math.round(basePrice).toLocaleString('ru-RU')} ₽</div>
3896                        </td>
3897                        <td>
3898                            <div style="font-weight: 600;">${Math.round(avgPrice).toLocaleString('ru-RU')} ₽</div>
3899                        </td>
3900                        <td>${product.revenue.toLocaleString('ru-RU')} ₽</td>
3901                        <td>${product.orders.toLocaleString('ru-RU')}</td>
3902                    </tr>
3903                `;
3904            });
3905            
3906            tableHTML += `
3907                    </tbody>
3908                </table>
3909            `;
3910            
3911            // Добавляем аналитику
3912            tableHTML += `
3913                <div class="ozon-parser-analytics">
3914                    <div class="ozon-parser-analytics-header">📊 Аналитика запроса</div>
3915                    
3916                    ${analytics.elasticity ? `
3917                    <div class="ozon-parser-analytics-section">
3918                        <div class="ozon-parser-analytics-card">
3919                            <div class="ozon-parser-analytics-card-title">Эластичность запроса</div>
3920                            <div class="ozon-parser-analytics-card-value">${analytics.elasticity.value}</div>
3921                            <div style="font-size: 12px; color: #666; margin-top: 8px;">
3922                                ${analytics.elasticity.interpretation}
3923                            </div>
3924                        </div>
3925                    </div>
3926                    ` : ''}
3927                    
3928                    <div class="ozon-parser-analytics-section">
3929                        <div class="ozon-parser-analytics-section-title">Рекомендованные цены</div>
3930                        <div class="ozon-parser-analytics-grid">
3931                            <div class="ozon-parser-analytics-card" style="border: 2px solid #6c757d;">
3932                                <div class="ozon-parser-analytics-card-title">⚡ Захват рынка</div>
3933                                <div class="ozon-parser-analytics-card-value" style="color: #6c757d;">${analytics.recommendedPrices.marketCapture.price.toLocaleString('ru-RU')} ₽</div>
3934                                <div style="font-size: 12px; color: #666; margin-top: 8px;">Оптимальная цена ниже текущей для максимизации прибыли</div>
3935                            </div>
3936                            <div class="ozon-parser-analytics-card" style="border: 2px solid #dc3545;">
3937                                <div class="ozon-parser-analytics-card-title">✅ Оптимальная</div>
3938                                <div class="ozon-parser-analytics-card-value" style="color: #dc3545;">${analytics.recommendedPrices.aggressive.price.toLocaleString('ru-RU')} ₽</div>
3939                                <div style="font-size: 12px; color: #666; margin-top: 8px;">Низкая цена для максимальных продаж и быстрого роста позиций</div>
3940                            </div>
3941                            <div class="ozon-parser-analytics-card" style="border: 2px solid #28a745;">
3942                                <div class="ozon-parser-analytics-card-title">🔥 Агрессивная</div>
3943                                <div class="ozon-parser-analytics-card-value" style="color: #28a745;">${analytics.recommendedPrices.optimal.price.toLocaleString('ru-RU')} ₽</div>
3944                                <div style="font-size: 12px; color: #666; margin-top: 8px;">Баланс между прибылью и объемом продаж</div>
3945                            </div>
3946                        </div>
3947                        <div class="ozon-parser-info">
3948                            💡 Расчет учитывает: средневзвешенную цену по выручке (вес 40%), оптимальную конверсию цена/заказы (вес 30%), медианную цену топ-10 (вес 30%). Товары из топ-5 позиций имеют удвоенный вес.
3949                        </div>
3950                    </div>
3951                    
3952                    ${analytics.brandRecommendations && Object.keys(analytics.brandRecommendations).length > 0 ? `
3953                    <div class="ozon-parser-analytics-section">
3954                        <div class="ozon-parser-analytics-section-title">🎯 Рекомендации для ваших брендов</div>
3955                        ${analytics.brandRecommendations.gls ? `
3956                        <div style="background: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; border: 2px solid #ffc107;">
3957                            <div style="font-size: 18px; font-weight: 600; margin-bottom: 15px; color: #333;">
3958                                ${analytics.brandRecommendations.gls.brand}
3959                            </div>
3960                            <div style="margin-bottom: 15px;">
3961                                <div style="font-size: 13px; color: #666; margin-bottom: 5px;">Текущие товары в выдаче: ${analytics.brandRecommendations.gls.currentProducts.length}</div>
3962                                <div style="font-size: 13px; color: #666; margin-bottom: 5px;">Средняя позиция: ${analytics.brandRecommendations.gls.avgPosition}</div>
3963                                <div style="font-size: 13px; color: #666;">Средняя цена: ${analytics.brandRecommendations.gls.avgPrice.toLocaleString('ru-RU')}</div>
3964                            </div>
3965                            <details style="margin-top: 10px;" open>
3966                                <summary style="cursor: pointer; font-size: 13px; color: #005bff; font-weight: 600;">Показать товары</summary>
3967                                <table class="ozon-parser-analytics-table" style="margin-top: 10px;">
3968                                    <thead>
3969                                        <tr>
3970                                            <th>SKU</th>
3971                                            <th>Название</th>
3972                                            <th>Позиция</th>
3973                                            <th>Текущая цена</th>
3974                                            <th>⚡ Захват рынка</th>
3975                                            <th>✅ Оптимальная</th>
3976                                            <th>🔥 Агрессивная</th>
3977                                        </tr>
3978                                    </thead>
3979                                    <tbody>
3980                                        ${analytics.brandRecommendations.gls.currentProducts.map(p => {
3981        const rec = p.recommendations;
3982        if (!rec) return `
3983                                            <tr>
3984                                                <td><a href="https://www.ozon.ru/product/${p.sku}" target="_blank" class="ozon-parser-sku-link">${p.sku}</a></td>
3985                                                <td style="max-width: 200px; white-space: normal;">${p.name || '—'}</td>
3986                                                <td>${p.position}</td>
3987                                                <td>${p.price.toLocaleString('ru-RU')} ₽</td>
3988                                                <td>—</td>
3989                                                <td>—</td>
3990                                                <td>—</td>
3991                                            </tr>
3992                                            `;
3993        return `
3994                                            <tr>
3995                                                <td><a href="https://www.ozon.ru/product/${p.sku}" target="_blank" class="ozon-parser-sku-link">${p.sku}</a></td>
3996                                                <td style="max-width: 200px; white-space: normal;">${p.name || '—'}</td>
3997                                                <td>${p.position}</td>
3998                                                <td>
3999                                                    <div style="font-weight: 600;">${p.price.toLocaleString('ru-RU')}</div>
4000                                                    <div style="font-size: 10px; color: #999; margin-top: 2px;">База: ${rec.currentBasePrice.toLocaleString('ru-RU')}</div>
4001                                                    <div style="font-size: 11px; color: #666; margin-top: 4px;">Выручка: ${p.revenue.toLocaleString('ru-RU')}</div>
4002                                                    <div style="font-size: 11px; color: #666;">Заказы: ${p.orders.toLocaleString('ru-RU')}</div>
4003                                                    ${rec.skuDiscount ? `
4004                                                    <div style="font-size: 11px; color: #ff6b00; font-weight: 600; margin-top: 4px;">
4005                                                        Скидка Ozon: ${rec.skuDiscount}%
4006                                                    </div>
4007                                                    ` : ''}
4008                                                    ${rec.currentProfit !== null ? `
4009                                                    <div style="font-size: 11px; color: #005bff; font-weight: 600; margin-top: 4px;">
4010                                                        Прибыль: ${rec.currentProfit.toFixed(0)}4011                                                    </div>
4012                                                    ` : ''}
4013                                                </td>
4014                                                <td style="background: #e9ecef;">
4015                                                    <div style="font-weight: 600; color: #6c757d;">${rec.marketCapture.toLocaleString('ru-RU')}</div>
4016                                                    <div style="font-size: 10px; color: #999; margin-top: 2px;">База: ${rec.marketCaptureBasePrice.toLocaleString('ru-RU')}</div>
4017                                                    <div style="font-size: 11px; color: ${rec.priceChange.marketCapture < 0 ? '#dc3545' : '#28a745'};">
4018                                                        ${rec.priceChange.marketCapture > 0 ? '+' : ''}${rec.priceChange.marketCapture}%
4019                                                    </div>
4020                                                    ${rec.forecast && rec.forecast.marketCapture ? `
4021                                                    <div style="font-size: 11px; color: ${rec.forecast.marketCapture.revenueChange >= 0 ? '#28a745' : '#dc3545'}; margin-top: 4px;">
4022                                                        Выручка: ${rec.forecast.marketCapture.revenueChange > 0 ? '+' : ''}${rec.forecast.marketCapture.revenueChange}%
4023                                                    </div>
4024                                                    <div style="font-size: 11px; color: ${rec.forecast.marketCapture.ordersChange >= 0 ? '#28a745' : '#dc3545'};">
4025                                                        Заказы: ${rec.forecast.marketCapture.ordersChange > 0 ? '+' : ''}${rec.forecast.marketCapture.ordersChange}%
4026                                                    </div>
4027                                                     ${rec.forecast.marketCapture.profit !== null ? `
4028                                                     <div style="font-size: 11px; color: ${rec.forecast.marketCapture.profitChange >= 0 ? '#28a745' : '#dc3545'}; font-weight: 600; margin-top: 2px;">
4029                                                         Прибыль: ${rec.forecast.marketCapture.profitChange > 0 ? '+' : ''}${rec.forecast.marketCapture.profitChange}%
4030                                                     </div>
4031                                                     <div style="font-size: 10px; color: #666;">
4032                                                         ${rec.forecast.marketCapture.profit.toFixed(0)}4033                                                     </div>
4034                                                     ` : ''}
4035                                                     ` : ''}
4036                                                </td>
4037                                                <td style="background: #d4edda;">
4038                                                    <div style="font-weight: 600; color: #28a745;">${rec.aggressive.toLocaleString('ru-RU')} ₽</div>
4039                                                    <div style="font-size: 10px; color: #999; margin-top: 2px;">База: ${rec.aggressiveBasePrice.toLocaleString('ru-RU')} ₽</div>
4040                                                    <div style="font-size: 11px; color: ${rec.priceChange.aggressive < 0 ? '#dc3545' : '#28a745'};">
4041                                                        ${rec.priceChange.aggressive > 0 ? '+' : ''}${rec.priceChange.aggressive}%
4042                                                    </div>
4043                                                    ${rec.forecast && rec.forecast.aggressive ? `
4044                                                    <div style="font-size: 11px; color: ${rec.forecast.aggressive.revenueChange >= 0 ? '#28a745' : '#dc3545'}; margin-top: 4px;">
4045                                                        Выручка: ${rec.forecast.aggressive.revenueChange > 0 ? '+' : ''}${rec.forecast.aggressive.revenueChange}%
4046                                                    </div>
4047                                                    <div style="font-size: 11px; color: ${rec.forecast.aggressive.ordersChange >= 0 ? '#28a745' : '#dc3545'};">
4048                                                        Заказы: ${rec.forecast.aggressive.ordersChange > 0 ? '+' : ''}${rec.forecast.aggressive.ordersChange}%
4049                                                    </div>
4050                                                     ${rec.forecast.aggressive.profit !== null ? `
4051                                                     <div style="font-size: 11px; color: ${rec.forecast.aggressive.profitChange >= 0 ? '#28a745' : '#dc3545'}; font-weight: 600; margin-top: 2px;">
4052                                                         Прибыль: ${rec.forecast.aggressive.profitChange > 0 ? '+' : ''}${rec.forecast.aggressive.profitChange}%
4053                                                     </div>
4054                                                     <div style="font-size: 10px; color: #666;">
4055                                                         ${rec.forecast.aggressive.profit.toFixed(0)}4056                                                     </div>
4057                                                     ` : ''}
4058                                                     ` : ''}
4059                                                </td>
4060                                                <td style="background: #fff3cd;">
4061                                                    <div style="font-weight: 600; color: #dc3545;">${rec.optimal.toLocaleString('ru-RU')} ₽</div>
4062                                                    <div style="font-size: 10px; color: #999; margin-top: 2px;">База: ${rec.optimalBasePrice.toLocaleString('ru-RU')} ₽</div>
4063                                                    <div style="font-size: 11px; color: ${rec.priceChange.optimal < 0 ? '#dc3545' : '#28a745'};">
4064                                                        ${rec.priceChange.optimal > 0 ? '+' : ''}${rec.priceChange.optimal}%
4065                                                    </div>
4066                                                    ${rec.forecast && rec.forecast.optimal ? `
4067                                                    <div style="font-size: 11px; color: ${rec.forecast.optimal.revenueChange >= 0 ? '#28a745' : '#dc3545'}; margin-top: 4px;">
4068                                                        Выручка: ${rec.forecast.optimal.revenueChange > 0 ? '+' : ''}${rec.forecast.optimal.revenueChange}%
4069                                                    </div>
4070                                                    <div style="font-size: 11px; color: ${rec.forecast.optimal.ordersChange >= 0 ? '#28a745' : '#dc3545'};">
4071                                                        Заказы: ${rec.forecast.optimal.ordersChange > 0 ? '+' : ''}${rec.forecast.optimal.ordersChange}%
4072                                                    </div>
4073                                                     ${rec.forecast.optimal.profit !== null ? `
4074                                                     <div style="font-size: 11px; color: ${rec.forecast.optimal.profitChange >= 0 ? '#28a745' : '#dc3545'}; font-weight: 600; margin-top: 2px;">
4075                                                         Прибыль: ${rec.forecast.optimal.profitChange > 0 ? '+' : ''}${rec.forecast.optimal.profitChange}%
4076                                                     </div>
4077                                                     <div style="font-size: 10px; color: #666;">
4078                                                         ${rec.forecast.optimal.profit.toFixed(0)}4079                                                     </div>
4080                                                     ` : ''}
4081                                                     ` : ''}
4082                                                </td>
4083                                            </tr>
4084                                            `;
4085    }).join('')}
4086                                    </tbody>
4087                                </table>
4088                            </details>
4089                        </div>
4090                        ` : ''}
4091                        ${analytics.brandRecommendations.skinphoria ? `
4092                        <div style="background: white; padding: 20px; border-radius: 8px; border: 2px solid #ffc107;">
4093                            <div style="font-size: 18px; font-weight: 600; margin-bottom: 15px; color: #333;">
4094                                ${analytics.brandRecommendations.skinphoria.brand}
4095                            </div>
4096                            <div style="margin-bottom: 15px;">
4097                                <div style="font-size: 13px; color: #666; margin-bottom: 5px;">Текущие товары в выдаче: ${analytics.brandRecommendations.skinphoria.currentProducts.length}</div>
4098                                <div style="font-size: 13px; color: #666; margin-bottom: 5px;">Средняя позиция: ${analytics.brandRecommendations.skinphoria.avgPosition}</div>
4099                                <div style="font-size: 13px; color: #666;">Средняя цена: ${analytics.brandRecommendations.skinphoria.avgPrice.toLocaleString('ru-RU')}</div>
4100                            </div>
4101                            <details style="margin-top: 10px;" open>
4102                                <summary style="cursor: pointer; font-size: 13px; color: #005bff; font-weight: 600;">Показать товары</summary>
4103                                <table class="ozon-parser-analytics-table" style="margin-top: 10px;">
4104                                    <thead>
4105                                        <tr>
4106                                            <th>SKU</th>
4107                                            <th>Название</th>
4108                                            <th>Позиция</th>
4109                                            <th>Текущая цена</th>
4110                                            <th>⚡ Захват рынка</th>
4111                                            <th>✅ Оптимальная</th>
4112                                            <th>🔥 Агрессивная</th>
4113                                        </tr>
4114                                    </thead>
4115                                    <tbody>
4116                                        ${analytics.brandRecommendations.skinphoria.currentProducts.map(p => {
4117        const rec = p.recommendations;
4118        if (!rec) return `
4119                                            <tr>
4120                                                <td><a href="https://www.ozon.ru/product/${p.sku}" target="_blank" class="ozon-parser-sku-link">${p.sku}</a></td>
4121                                                <td style="max-width: 200px; white-space: normal;">${p.name || '—'}</td>
4122                                                <td>${p.position}</td>
4123                                                <td>${p.price.toLocaleString('ru-RU')} ₽</td>
4124                                                <td>—</td>
4125                                                <td>—</td>
4126                                                <td>—</td>
4127                                            </tr>
4128                                            `;
4129        return `
4130                                            <tr>
4131                                                <td><a href="https://www.ozon.ru/product/${p.sku}" target="_blank" class="ozon-parser-sku-link">${p.sku}</a></td>
4132                                                <td style="max-width: 200px; white-space: normal;">${p.name || '—'}</td>
4133                                                <td>${p.position}</td>
4134                                                <td>
4135                                                    <div style="font-weight: 600;">${p.price.toLocaleString('ru-RU')}</div>
4136                                                    <div style="font-size: 10px; color: #999; margin-top: 2px;">База: ${rec.currentBasePrice.toLocaleString('ru-RU')}</div>
4137                                                    <div style="font-size: 11px; color: #666; margin-top: 4px;">Выручка: ${p.revenue.toLocaleString('ru-RU')}</div>
4138                                                    <div style="font-size: 11px; color: #666;">Заказы: ${p.orders.toLocaleString('ru-RU')}</div>
4139                                                    ${rec.skuDiscount ? `
4140                                                    <div style="font-size: 11px; color: #ff6b00; font-weight: 600; margin-top: 4px;">
4141                                                        Скидка Ozon: ${rec.skuDiscount}%
4142                                                    </div>
4143                                                    ` : ''}
4144                                                    ${rec.currentProfit !== null ? `
4145                                                    <div style="font-size: 11px; color: #005bff; font-weight: 600; margin-top: 4px;">
4146                                                        Прибыль: ${rec.currentProfit.toFixed(0)}4147                                                    </div>
4148                                                    ` : ''}
4149                                                </td>
4150                                                <td style="background: #e9ecef;">
4151                                                    <div style="font-weight: 600; color: #6c757d;">${rec.marketCapture.toLocaleString('ru-RU')}</div>
4152                                                    <div style="font-size: 10px; color: #999; margin-top: 2px;">База: ${rec.marketCaptureBasePrice.toLocaleString('ru-RU')}</div>
4153                                                    <div style="font-size: 11px; color: ${rec.priceChange.marketCapture < 0 ? '#dc3545' : '#28a745'};">
4154                                                        ${rec.priceChange.marketCapture > 0 ? '+' : ''}${rec.priceChange.marketCapture}%
4155                                                    </div>
4156                                                    ${rec.forecast && rec.forecast.marketCapture ? `
4157                                                    <div style="font-size: 11px; color: ${rec.forecast.marketCapture.revenueChange >= 0 ? '#28a745' : '#dc3545'}; margin-top: 4px;">
4158                                                        Выручка: ${rec.forecast.marketCapture.revenueChange > 0 ? '+' : ''}${rec.forecast.marketCapture.revenueChange}%
4159                                                    </div>
4160                                                    <div style="font-size: 11px; color: ${rec.forecast.marketCapture.ordersChange >= 0 ? '#28a745' : '#dc3545'};">
4161                                                        Заказы: ${rec.forecast.marketCapture.ordersChange > 0 ? '+' : ''}${rec.forecast.marketCapture.ordersChange}%
4162                                                    </div>
4163                                                     ${rec.forecast.marketCapture.profit !== null ? `
4164                                                     <div style="font-size: 11px; color: ${rec.forecast.marketCapture.profitChange >= 0 ? '#28a745' : '#dc3545'}; font-weight: 600; margin-top: 2px;">
4165                                                         Прибыль: ${rec.forecast.marketCapture.profitChange > 0 ? '+' : ''}${rec.forecast.marketCapture.profitChange}%
4166                                                     </div>
4167                                                     <div style="font-size: 10px; color: #666;">
4168                                                         ${rec.forecast.marketCapture.profit.toFixed(0)}4169                                                     </div>
4170                                                     ` : ''}
4171                                                     ` : ''}
4172                                                </td>
4173                                                <td style="background: #d4edda;">
4174                                                    <div style="font-weight: 600; color: #28a745;">${rec.aggressive.toLocaleString('ru-RU')} ₽</div>
4175                                                    <div style="font-size: 10px; color: #999; margin-top: 2px;">База: ${rec.aggressiveBasePrice.toLocaleString('ru-RU')} ₽</div>
4176                                                    <div style="font-size: 11px; color: ${rec.priceChange.aggressive < 0 ? '#dc3545' : '#28a745'};">
4177                                                        ${rec.priceChange.aggressive > 0 ? '+' : ''}${rec.priceChange.aggressive}%
4178                                                    </div>
4179                                                    ${rec.forecast && rec.forecast.aggressive ? `
4180                                                    <div style="font-size: 11px; color: ${rec.forecast.aggressive.revenueChange >= 0 ? '#28a745' : '#dc3545'}; margin-top: 4px;">
4181                                                        Выручка: ${rec.forecast.aggressive.revenueChange > 0 ? '+' : ''}${rec.forecast.aggressive.revenueChange}%
4182                                                    </div>
4183                                                    <div style="font-size: 11px; color: ${rec.forecast.aggressive.ordersChange >= 0 ? '#28a745' : '#dc3545'};">
4184                                                        Заказы: ${rec.forecast.aggressive.ordersChange > 0 ? '+' : ''}${rec.forecast.aggressive.ordersChange}%
4185                                                    </div>
4186                                                     ${rec.forecast.aggressive.profit !== null ? `
4187                                                     <div style="font-size: 11px; color: ${rec.forecast.aggressive.profitChange >= 0 ? '#28a745' : '#dc3545'}; font-weight: 600; margin-top: 2px;">
4188                                                         Прибыль: ${rec.forecast.aggressive.profitChange > 0 ? '+' : ''}${rec.forecast.aggressive.profitChange}%
4189                                                     </div>
4190                                                     <div style="font-size: 10px; color: #666;">
4191                                                         ${rec.forecast.aggressive.profit.toFixed(0)}4192                                                     </div>
4193                                                     ` : ''}
4194                                                     ` : ''}
4195                                                </td>
4196                                                <td style="background: #fff3cd;">
4197                                                    <div style="font-weight: 600; color: #dc3545;">${rec.optimal.toLocaleString('ru-RU')} ₽</div>
4198                                                    <div style="font-size: 10px; color: #999; margin-top: 2px;">База: ${rec.optimalBasePrice.toLocaleString('ru-RU')} ₽</div>
4199                                                    <div style="font-size: 11px; color: ${rec.priceChange.optimal < 0 ? '#dc3545' : '#28a745'};">
4200                                                        ${rec.priceChange.optimal > 0 ? '+' : ''}${rec.priceChange.optimal}%
4201                                                    </div>
4202                                                    ${rec.forecast && rec.forecast.optimal ? `
4203                                                    <div style="font-size: 11px; color: ${rec.forecast.optimal.revenueChange >= 0 ? '#28a745' : '#dc3545'}; margin-top: 4px;">
4204                                                        Выручка: ${rec.forecast.optimal.revenueChange > 0 ? '+' : ''}${rec.forecast.optimal.revenueChange}%
4205                                                    </div>
4206                                                    <div style="font-size: 11px; color: ${rec.forecast.optimal.ordersChange >= 0 ? '#28a745' : '#dc3545'};">
4207                                                        Заказы: ${rec.forecast.optimal.ordersChange > 0 ? '+' : ''}${rec.forecast.optimal.ordersChange}%
4208                                                    </div>
4209                                                     ${rec.forecast.optimal.profit !== null ? `
4210                                                     <div style="font-size: 11px; color: ${rec.forecast.optimal.profitChange >= 0 ? '#28a745' : '#dc3545'}; font-weight: 600; margin-top: 2px;">
4211                                                         Прибыль: ${rec.forecast.optimal.profitChange > 0 ? '+' : ''}${rec.forecast.optimal.profitChange}%
4212                                                     </div>
4213                                                     <div style="font-size: 10px; color: #666;">
4214                                                         ${rec.forecast.optimal.profit.toFixed(0)}4215                                                     </div>
4216                                                     ` : ''}
4217                                                     ` : ''}
4218                                                </td>
4219                                            </tr>
4220                                            `;
4221    }).join('')}
4222                                    </tbody>
4223                                </table>
4224                            </details>
4225                        </div>
4226                        ` : ''}
4227                    </div>
4228                    ` : ''}
4229                    
4230                    <div class="ozon-parser-analytics-section">
4231                        <div class="ozon-parser-analytics-section-title">Общая статистика</div>
4232                        <div class="ozon-parser-analytics-grid">
4233                            <div class="ozon-parser-analytics-card">
4234                                <div class="ozon-parser-analytics-card-title">Общая выручка</div>
4235                                <div class="ozon-parser-analytics-card-value">${analytics.totalRevenue.toLocaleString('ru-RU')}</div>
4236                            </div>
4237                            <div class="ozon-parser-analytics-card">
4238                                <div class="ozon-parser-analytics-card-title">Всего заказов</div>
4239                                <div class="ozon-parser-analytics-card-value">${analytics.totalOrders.toLocaleString('ru-RU')}</div>
4240                            </div>
4241                            <div class="ozon-parser-analytics-card">
4242                                <div class="ozon-parser-analytics-card-title">Средний чек</div>
4243                                <div class="ozon-parser-analytics-card-value">${analytics.avgPrice.toLocaleString('ru-RU')}</div>
4244                            </div>
4245                        </div>
4246                    </div>
4247                    
4248                    <div class="ozon-parser-analytics-section">
4249                        <div class="ozon-parser-analytics-section-title">Топ-5 товаров по выручке</div>
4250                        <table class="ozon-parser-analytics-table">
4251                            <thead>
4252                                <tr>
4253                                    <th>Товар</th>
4254                                    <th>Бренд</th>
4255                                    <th>Цена</th>
4256                                    <th>Выручка</th>
4257                                    <th>Доля</th>
4258                                </tr>
4259                            </thead>
4260                            <tbody>
4261            `;
4262            
4263            analytics.topProducts.forEach(product => {
4264                tableHTML += `
4265                    <tr>
4266                        <td style="max-width: 300px; white-space: normal;">${product.name || '—'}</td>
4267                        <td>${product.brand}</td>
4268                        <td>${product.price.toLocaleString('ru-RU')}</td>
4269                        <td>${product.revenue.toLocaleString('ru-RU')}</td>
4270                        <td>${product.revenueShare}%</td>
4271                    </tr>
4272                `;
4273            });
4274            
4275            tableHTML += `
4276                            </tbody>
4277                        </table>
4278                    </div>
4279                    
4280                    <div class="ozon-parser-analytics-section">
4281                        <div class="ozon-parser-analytics-section-title">Ценовые сегменты</div>
4282                        <table class="ozon-parser-analytics-table">
4283                            <thead>
4284                                <tr>
4285                                    <th>Сегмент</th>
4286                                    <th>Диапазон цен</th>
4287                                    <th>Товаров</th>
4288                                    <th>Средняя цена</th>
4289                                    <th>Выручка</th>
4290                                    <th>Доля выручки</th>
4291                                </tr>
4292                            </thead>
4293                            <tbody>
4294            `;
4295            
4296            analytics.priceSegments.forEach(segment => {
4297                tableHTML += `
4298                    <tr>
4299                        <td>${segment.name}</td>
4300                        <td>${segment.priceRange}</td>
4301                        <td>${segment.count}</td>
4302                        <td>${segment.avgPrice.toLocaleString('ru-RU')}</td>
4303                        <td>${segment.revenue.toLocaleString('ru-RU')}</td>
4304                        <td>
4305                            <div style="display: flex; align-items: center; gap: 10px;">
4306                                <div class="ozon-parser-analytics-bar" style="width: ${segment.revenueShare}%; min-width: 20px;"></div>
4307                                <span>${segment.revenueShare}%</span>
4308                            </div>
4309                        </td>
4310                    </tr>
4311                `;
4312            });
4313            
4314            tableHTML += `
4315                            </tbody>
4316                        </table>
4317                    </div>
4318                </div>
4319            `;
4320            
4321            container.innerHTML = tableHTML;
4322            console.log('Ozon Product Parser: Results displayed successfully');
4323        } catch (error) {
4324            console.error('Ozon Product Parser: Error in displayResults:', error);
4325            container.innerHTML = '<p>Ошибка при отображении результатов: ' + error.message + '</p>';
4326        }
4327    }
4328
4329    // Инициализация
4330    function init() {
4331        console.log('Ozon Product Parser: Initializing...');
4332        
4333        if (document.readyState === 'loading') {
4334            document.addEventListener('DOMContentLoaded', () => {
4335                addStyles();
4336                createUI();
4337                continueParsingIfActive();
4338                continueDiscountCalculationIfActive();
4339            });
4340        } else {
4341            addStyles();
4342            createUI();
4343            continueParsingIfActive();
4344            continueDiscountCalculationIfActive();
4345        }
4346    }
4347
4348    init();
4349})();
Ozon Product Parser | Robomonkey