Покажи где я могу изменить, чтобы мой веб сайт на фласке при автоматическом обновлении не моргал, а то неприятно, но при этом обновлял записи которые приходят
let isEditing = false;
let searchTimeout;
let lastUpdateTime = 0;
const updateInterval = 1000;
let eventSource;
let lastData = null;

// Глобальные переменные для отслеживания сортировки
let globalSortColumn = null;
let globalSortOrder = 'asc';

function getSelectedDate() {
    return $('#datepicker').val();
}

function updateTable(force = false, highlightNew = false) {
    return new Promise((resolve, reject) => {
        const now = Date.now();
        if (!force && now - lastUpdateTime < updateInterval) {
            resolve();
            return;
        }
        if (isEditing) {
            resolve();
            return;
        }

        $.ajax({
            url: '/get_table_data',
            type: 'GET',
            data: { date: getSelectedDate() },
            success: function(response) {
                var tableBody = $('#tableBody');

                // Обновляем содержимое таблицы
                updateTableContent(response.data, tableBody, highlightNew);

                // Применяем текущую сортировку
                if (globalSortColumn !== null) {
                    sortTable(globalSortColumn, globalSortOrder);
                }

                updateStatistics();
                lastUpdateTime = now;

                // Проверяем и подсвечиваем дубликаты
                highlightDuplicates(response.duplicates);

                resolve();
            },
            error: function(xhr, status, error) {
                console.error('Error updating table:', error);
                reject(error);
            }
        });
    });
}

function highlightDuplicates(duplicates) {
    // Очищаем предыдущие выделения
    $('#tableBody td').removeClass('duplicate-highlight');

    // Подсвечиваем дубликаты номеров
    for (let number in duplicates.numbers) {
        duplicates.numbers[number].forEach(index => {
            $(`#tableBody tr:eq(${index}) td:nth-child(3)`).addClass('duplicate-highlight');
        });
    }

    // Подсвечиваем дубликаты серийных номеров
    for (let serial in duplicates.serials) {
        duplicates.serials[serial].forEach(index => {
            $(`#tableBody tr:eq(${index}) td:nth-child(4)`).addClass('duplicate-highlight');
        });
    }
}

function updateCell(cell, newValue, rowIndex, cellIndex) {
    console.log(`Updating cell: index=${cellIndex}, value=${newValue}, rowIndex=${rowIndex}`);

    if (cellIndex === 4) { // Столбец "Графа"
        if (newValue && newValue.startsWith('DL')) {
            cell.text(newValue)
                .css('cursor', 'pointer')
                .off('click')
                .on('click', function() {
                    copyToClipboard(newValue);
                    $(this).addClass('copied');
                    setTimeout(() => {
                        $(this).removeClass('copied');
                    }, 200);
                });
        } else {
            cell.html('<button class="generate-button">Генерировать</button>')
                .find('button')
                .click(function() {
                    generateDL(rowIndex);
                });
        }
    } else if (cellIndex === 6) { // Столбец "Время упаковки"
        const dlCell = cell.parent().find('td').eq(4);
        const dlValue = dlCell.text().trim();
        if (dlValue.startsWith('DL') && !newValue) {
            cell.html('<button class="pack-button">Упаковать</button>')
                .find('button')
                .click(function() {
                    packItem(rowIndex);
                });
        } else {
            cell.text(newValue);
        }
    } else if (cellIndex === 7) { // Столбец "Тип АРМА"
        if (!newValue || newValue.trim() === '') {
            newValue = 'RPA';
        }
        cell.text(newValue)
            .toggleClass('editable', true)
            .attr('data-column', cellIndex);
    } else {
        cell.text(newValue)
            .toggleClass('editable', cellIndex === 2 || cellIndex === 3)
            .attr('data-column', cellIndex);
    }
}

function sortTable(column, order) {
    const rows = $('#tableBody > tr').get();

    rows.sort(function(a, b) {
        let A = $(a).children('td').eq(column).text().trim();
        let B = $(b).children('td').eq(column).text().trim();

        // Обработка пустых значений
        if (A === '' && B === '') return 0;
        if (A === '') return order === 'asc' ? 1 : -1;
        if (B === '') return order === 'asc' ? -1 : 1;

        // Различные типы данных в зависимости от столбца
        if (column === 1 || column === 6) {  // Столбцы с датой или временем
            const dateA = new Date(A.replace(/(\d{2})\.(\d{2})\.(\d{4}) (\d{2}):(\d{2}):(\d{2})/, '$3-$2-$1T$4:$5:$6'));
            const dateB = new Date(B.replace(/(\d{2})\.(\d{2})\.(\d{4}) (\d{2}):(\d{2}):(\d{2})/, '$3-$2-$1T$4:$5:$6'));
            return order === 'asc' ? dateA - dateB : dateB - dateA;
        }

        if (column === 0 || column === 2) {  // Столбцы с числами (№ и Номер)
            const numA = parseFloat(A) || 0;
            const numB = parseFloat(B) || 0;
            return order === 'asc' ? numA - numB : numB - numA;
        }

        if (column === 5) {  // Столбец с именем workers
            A = A.toLowerCase();
            B = B.toLowerCase();
        }

        // Строковое сравнение для остальных столбцов
        A = A.toUpperCase();
        B = B.toUpperCase();
        if (A < B) return order === 'asc' ? -1 : 1;
        if (A > B) return order === 'asc' ? 1 : -1;
        return 0;
    });

    // Очистка тела таблицы
    $('#tableBody').empty();

    // Добавление отсортированных строк
    $.each(rows, function(index, row) {
        const $row = $(row);

        // Восстановление функциональности кнопок
        const dlCell = $row.find('td:eq(4)');
        const packCell = $row.find('td:eq(6)'); // Столбец "Время упаковки"
        const dlValue = dlCell.text().trim();
        const packValue = packCell.text().trim();

        if (!dlValue.startsWith('DL')) {
            dlCell.html('<button class="generate-button">Генерировать</button>')
                .find('button')
                .click(function() {
                    // Нужно корректировать rowIndex для отсортированных строк
                    const newIndex = $row.index();
                    generateDL(newIndex);
                });
        }

        if (dlValue.startsWith('DL') && !packValue) {
            packCell.html('<button class="pack-button">Упаковать</button>')
                .find('button')
                .click(function() {
                    const newIndex = $row.index();
                    packItem(newIndex);
                });
        }

        $('#tableBody').append($row);
    });

    checkDuplicates();
}

function generateDL(rowIndex) {
    console.log(`Generating DL for row ${rowIndex}`);
    const selectedDate = getSelectedDate();
    console.log("Selected date:", selectedDate);

    $.ajax({
        url: '/generate_dl',
        type: 'POST',
        contentType: 'application/json',
        data: JSON.stringify({
            row_index: rowIndex,
            date: selectedDate
        }),
        success: function(response) {
            if (response.status === 'success') {
                console.log('DL generated successfully:', response.dl_number);
                var row = $(`#tableBody tr:eq(${rowIndex})`);
                var dlCell = row.find('td:eq(4)');
                var packCell = row.find('td:eq(6)'); // Столбец "Время упаковки"
                
                dlCell.text(response.dl_number);
                
                // Добавляем кнопку упаковки
                console.log('Adding pack button after DL generation');
                packCell.html('<button class="pack-button">Упаковать</button>')
                    .find('button')
                    .click(function() {
                        packItem(rowIndex);
                    });
            } else {
                alert('Ошибка при генерации DL: ' + response.message);
            }
        },
        error: function(xhr, status, error) {
            console.error('Error generating DL:', status, error);
            alert('Ошибка при генерации DL: ' + error);
        }
    });
}

function packItem(rowIndex) {
    console.log(`Packing item for row ${rowIndex}`);
    const selectedDate = getSelectedDate();
    console.log("Selected date:", selectedDate);

    $.ajax({
        url: '/pack_item',
        type: 'POST',
        contentType: 'application/json',
        data: JSON.stringify({
            row_index: rowIndex,
            date: selectedDate
        }),
        success: function(response) {
            console.log("Server response:", response);
            if (response.status === 'success') {
                var row = $(`#tableBody tr:eq(${rowIndex})`);
                row.addClass('packed-row');
                var cell = row.find('td:eq(6)'); // Столбец "Время упаковки"
                cell.text(response.pack_time);
                updateStatistics();
            
            } else {
                alert('Ошибка при упаковке: ' + response.message);
            }
        },
        error: function(xhr, status, error) {
            console.error("AJAX error:", xhr, status, error);
            alert('Ошибка при упаковке: ' + error);
        }
    });
}

function areSimilarNumbers(num1, num2) {
    // Очищаем строки от нецифровых символов и пробелов
    const clean1 = num1.toString().replace(/\D/g, '').trim();
    const clean2 = num2.toString().replace(/\D/g, '').trim();
    
    // Если строки пустые или разной длины - не дубликаты
    if (!clean1 || !clean2 || clean1.length !== clean2.length) {
        return false;
    }
    
    // Проверяем на точное совпадение
    return clean1 === clean2;
}

function checkDuplicates() {
    console.log("Checking for duplicates...");
    const rows = $('#tableBody tr');
    
    // Очищаем предыдущие выделения
    rows.find('td').removeClass('duplicate-highlight');
    
    // Создаем массивы для хранения номеров и их индексов
    const numbers = new Map();
    const serials = new Map();
    
    // Собираем все номера и серийные номера
    rows.each(function(index) {
        const number = $(this).find('td:nth-child(3)').text().trim();
        const serial = $(this).find('td:nth-child(4)').text().trim();
        
        if (number) {
            if (!numbers.has(number)) {
                numbers.set(number, []);
            }
            numbers.get(number).push(index);
        }
        
        if (serial) {
            if (!serials.has(serial)) {
                serials.set(serial, []);
            }
            serials.get(serial).push(index);
        }
    });
    
    // Подсвечиваем дубликаты
    let duplicatesFound = false;
    
    // Проверяем номера
    numbers.forEach((indices, number) => {
        if (indices.length > 1) {
            indices.forEach(index => {
                rows.eq(index).find('td:nth-child(3)').addClass('duplicate-highlight');
            });
            duplicatesFound = true;
        }
    });
    
    // Проверяем серийные номера
    serials.forEach((indices, serial) => {
        if (indices.length > 1) {
            indices.forEach(index => {
                rows.eq(index).find('td:nth-child(4)').addClass('duplicate-highlight');
            });
            duplicatesFound = true;
        }
    });

    return duplicatesFound;
}

function appendNewRow(tableBody, rowData) {
    console.log("Adding new row:", rowData);
    var tr = $('<tr>');
    
    // Проверяем наличие времени упаковки
    if (rowData[6]) {
        tr.addClass('packed-row');
    }
    
    // Добавляем все ячейки
    rowData.forEach(function(cellValue, index) {
        var td = $('<td>');
        tr.append(td);
        
        // Обновляем ячейку
        setTimeout(() => {
            updateCell(td, cellValue, tr.index(), index);
        }, 0);
    });
    
    tableBody.append(tr);
    
    // Дополнительная проверка после добавления строки
    setTimeout(() => {
        const dlCell = tr.find('td').eq(4);
        const packCell = tr.find('td').eq(6); // Столбец "Время упаковки"
        const dlValue = dlCell.text().trim();
        const packValue = packCell.text().trim();
        
        if (dlValue.startsWith('DL') && !packValue) {
            console.log('Post-check: Adding pack button for DL:', dlValue);
            packCell.html('<button class="pack-button">Упаковать</button>')
                .find('button')
                .click(function() {
                    packItem(tr.index());
                });
        }
        
        // Проверяем дубликаты после добавления новой строки
        checkDuplicates();
    }, 100);
}

function updateTableContent(data, tableBody, highlightNew = false) {
    tableBody.empty();  // Очищаем текущее содержимое таблицы
    data.forEach(function(rowData, index) {
        appendNewRow(tableBody, rowData);
        if (highlightNew && index === data.length - 1) {
            // Можно добавить подсветку новой строки, если нужно
            // Например:
            // $('tr:last').addClass('highlight-new').fadeOut(2000, function() {
            //     $(this).removeClass('highlight-new');
            // });
        }
    });
    checkDuplicates();  // Проверяем дубликаты после обновления таблицы
}

function scheduleNextUpdate() {
    setTimeout(() => {
        if (!isEditing) {
            updateTable(false, false).then(() => {
                scheduleNextUpdate();
            });
        } else {
            scheduleNextUpdate();
        }
    }, 3000);
}

// Инициализация `EventSource`
function initializeEventSource() {
    if (eventSource) {
        eventSource.close();
    }

    eventSource = new EventSource('/stream');
    
    eventSource.onmessage = function(event) {
        const data = JSON.parse(event.data);
        if (data.type === 'update' && data.date === getSelectedDate()) {
            updateTable(true);
        }
    };

    eventSource.onerror = function(error) {
        console.error('EventSource failed:', error);
        eventSource.close();
        setTimeout(initializeEventSource, 5000);  // Попытка переподключения через 5 секунд
    };
}

// Обработчики событий для таблицы
$(document).ready(function() {
    initializeDateSelectors(); // Убедитесь, что эта функция определена
    updateTable();
    updateStatistics(); // Убедитесь, что эта функция определена
    initializeEventSource();
    scheduleNextUpdate();

    // Обработчик сортировки
    $('.sortable').click(function() {
        const column = $(this).data('column');
        const order = $(this).hasClass('asc') ? 'desc' : 'asc';
        
        // Удаляем классы сортировки со всех заголовков
        $('.sortable').removeClass('asc desc');
        // Добавляем класс текущей сортировки
        $(this).addClass(order);
        
        // Сохраняем текущие параметры сортировки
        globalSortColumn = column;
        globalSortOrder = order;
        
        // Сортируем таблицу
        sortTable(column, order);
    });

    // Обработчик поиска
    $('#searchButton').click(function() {
        searchNumber();
    });
    $('#searchInput').keypress(function(e) {
        if (e.which === 13) {
            searchNumber();
        }
    });
});

// Обработчик изменения даты
$('#datepicker').on('change', function() {
    initializeEventSource();
    updateTable();
});

// Обработчик экспорта в Excel
$(document).ready(function() {
    $('.export-button').on('click', function(e) {
        e.preventDefault();
        
        // Получаем информацию о сортировке
        const sortableColumn = $('.sortable.asc, .sortable.desc');
        const sortColumn = sortableColumn.length ? sortableColumn.data('column') : '';
        const sortOrder = $('.sortable.asc').length ? 'asc' : 'desc';
        
        // Формируем URL
        const date = getSelectedDate();
        const queryParams = new URLSearchParams({
            date: date,
            sort_column: sortColumn || '',
            sort_order: sortOrder
        });
        
        // Создаем и отправляем запрос
        fetch(`/export_excel?${queryParams.toString()}`)
        .then(response => {
            if (response.ok) {
                return response.blob();
            }
            throw new Error('Network response was not ok.');
        })
        .then(blob => {
            // Создаем ссылку для скачивания
            const url = window.URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = `export_${date}.xlsx`;
            document.body.appendChild(a);
            a.click();
            window.URL.revokeObjectURL(url);
            a.remove();
        })
        .catch(error => {
            console.error('Error:', error);
            alert('Ошибка при экспорте файла');
        });
    });
});

// Инициализация выбора даты с помощью flatpickr
let currentDate;

$(document).ready(function() {
    flatpickr("#datepicker", {
        dateFormat: "Y-m-d",
        defaultDate: "today",
        onChange: function(selectedDates, dateStr, instance) {
            currentDate = dateStr;
            updateTable();
        }
    });

    currentDate = new Date().toISOString().split('T')[0];
    initializeEventSource();
    updateTable();
    updateStatistics();
});

// Обработчик двойного клика для редактирования ячеек
$(document).on('dblclick', '.editable', function() {
    isEditing = true;
    var td = $(this);
    var currentValue = td.text();
    var input = $('<input type="text">')
        .val(currentValue)
        .addClass('edit-input');
    
    td.html(input);
    input.focus();

    input.on('blur', function() {
        var newValue = $(this).val();
        var rowIndex = td.parent().index();
        var columnIndex = td.attr('data-column');
        
        const url = columnIndex === '2' ? '/update_number' : '/update_serial';
        
        $.ajax({
            url: url,
            type: 'POST',
            contentType: 'application/json',
            data: JSON.stringify({
                row_index: rowIndex,
                new_value: newValue,
                date: getSelectedDate()
            }),
            success: function(response) {
                if (response.status === 'success') {
                    td.text(newValue);
                    const duplicatesFound = checkDuplicates();
                    console.log("Duplicates found after edit:", duplicatesFound)
                } else {
                    alert('Ошибка при обновлении значения');
                    td.text(currentValue);
                }
                isEditing = false;
            },
            error: function() {
                alert('Ошибка при отправке данных');
                td.text(currentValue);
                isEditing = false;
            }
        });
    });

    input.on('keypress', function(e) {
        if (e.which === 13) {
            $(this).blur();
        }
    });
});

function searchNumber() {
    const searchValue = $('#searchInput').val().trim();
    if (!searchValue) return;

    // Удаляем предыдущую подсветку
    $('.highlight-search').removeClass('highlight-search');

    const rows = $('#tableBody tr');
    let found = false;

    rows.each(function() {
        const numberCell = $(this).find('td:nth-child(3)');
        const cellText = numberCell.text().trim();
        if (cellText === searchValue) {
            numberCell.addClass('highlight-search');
            
            // Прокрутка к найденной строке
            $('.table-container').animate({
                scrollTop: $(this).offset().top - $('.table-container').offset().top + $('.table-container').scrollTop() - 100
            }, 500);

            found = true;

            // Устанавливаем таймер на удаление подсветки через 3 секунды
            setTimeout(() => {
                numberCell.removeClass('highlight-search');
            }, 3000);

            // Выводим сообщение в консоль для отладки
            console.log('Найдено совпадение:', cellText);

            return false; // Прерываем цикл, так как нашли нужную строку
        }
    });

    if (!found) {
        alert('Точное совпадение номера не найдено');
    }
}