Ozon Product Parser

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

Size

267.9 KB

Version

1.8.58

Created

Apr 3, 2026

Updated

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