кнопка генерировать нажимается отображается DL потом вновь появляется кнопка генерировать на этой же записи. нужно чтобы кнопка генерировать функицонировала
from flask import Flask, render_template, request, redirect, make_response, jsonify, send_file, Response
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.sql import func
from datetime import datetime, timedelta, date
import pandas as pd
from io import BytesIO
import json
import queue
import logging
from sqlalchemy import cast, Integer


app = Flask(__name__)

app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///table_data.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)



class TableRow(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    date = db.Column(db.String(10), nullable=False)
    row_number = db.Column(db.String(10))
    timestamp = db.Column(db.String(50))
    number = db.Column(db.String(50))
    serial = db.Column(db.String(50))
    dl_number = db.Column(db.String(50))
    worker_name = db.Column(db.String(100))
    pack_time = db.Column(db.String(50))
    last_graf = db.Column(db.String(50))
    arma_type = db.Column(db.String(50))
    


class DLCounter(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    date = db.Column(db.String(10), unique=True, nullable=False)
    counter = db.Column(db.Integer, nullable=False)

    def __repr__(self):
        return f'<DLCounter {self.date}: {self.counter}>'


class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    login_history = db.relationship('LoginHistory', backref='user', lazy=True)


class LoginHistory(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    login_date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)

class ActiveUser(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(100), nullable=False)
    date = db.Column(db.Date, nullable=False)
    record_count = db.Column(db.Integer, default=0)

    def __repr__(self):
        return f'<ActiveUser {self.username} on {self.date}>'

tables = {}
table = [['№', 'Время отправки', 'Номер ', 'Serial', 'Графа', 'Имя отправителя','Время упаковки' ,'Тип АРМА', 'last_graf', 'Действие']]
# Добавляем счетчик для номеров записей
row_counter = 1
# Создаем очередь для событий
update_queue = queue.Queue()



@app.route('/stream')
def stream():
    def event_stream():
        while True:
            # Ваш код для генерации сообщений
            yield f'data: {json.dumps({"type": "update", "date": "2023-10-01"})}\n\n'
    return Response(event_stream(), mimetype='text/event-stream')

@app.route('/get_table_data')
def get_table_data():
    date = request.args.get('date')
    filter_arma = request.args.get('filter_arma')  # Новый параметр фильтра

    # Начинаем построение запроса
    query = TableRow.query.filter_by(date=date)
    
    # Если фильтр активирован, добавляем условие
    if filter_arma == 'RPA':
        query = query.filter_by(arma_type='RPA')
    
    rows = query.all()
    data = []
    numbers = {}
    serials = {}

    for row in rows:
        data.append({
            'id': row.id,  # Добавляем ID записи
            'row_number': row.row_number,
            'timestamp': row.timestamp,
            'number': row.number,
            'serial': row.serial,
            'dl_number': row.dl_number,
            'worker_name': row.worker_name,
            'pack_time': row.pack_time,
            'arma_type': row.arma_type,
            'last_graf': row.last_graf
        })

        # Собираем информацию о дубликатах
        if row.number:
            clean_number = ''.join(filter(str.isdigit, row.number))
            if clean_number in numbers:
                numbers[clean_number].append(len(data) - 1)  # Индекс строки
            else:
                numbers[clean_number] = [len(data) - 1]

        if row.serial:
            clean_serial = ''.join(filter(str.isdigit, row.serial))
            if clean_serial in serials:
                serials[clean_serial].append(len(data) - 1)  # Индекс строки
            else:
                serials[clean_serial] = [len(data) - 1]

    # Фильтруем только реальные дубликаты
    duplicate_numbers = {k: v for k, v in numbers.items() if len(v) > 1}
    duplicate_serials = {k: v for k, v in serials.items() if len(v) > 1}

    return jsonify({
        "data": data,
        "duplicates": {
            "numbers": duplicate_numbers,
            "serials": duplicate_serials
        }
    })

@app.route('/update_table', methods=['POST'])
def update_table():
    data = request.json
    print(data)
    date = data['date']
    worker_name = data['worker_name']
    arma_type = data['arma_type']
    
    # Получаем количество существующих записей для этой даты
    row_count = TableRow.query.filter_by(date=date).count() + 1
    
    # Создаём новую строку в таблице
    if not data.get('number') and not data.get('serial'):
        new_row = TableRow(
            date=date,
            row_number=str(row_count),
            timestamp=data['timestamp'],
            number='',  # Пустое значение для номера
            serial='',  # Пустое значение для серийного номера
            dl_number='',  # Пустое значение для графы
            worker_name=worker_name,
            arma_type=arma_type,
        )
    elif not data.get('number'):
        new_row = TableRow(
            date=date,
            row_number=str(row_count),
            timestamp=data['timestamp'],
            number='',  # Пустое значение для номера
            serial=data['serial'],
            dl_number='',  # Пустое значение для графы
            worker_name=worker_name,
            arma_type=arma_type,
        )
    else:
        new_row = TableRow(
            date=date,
            row_number=str(row_count),
            timestamp=data['timestamp'],
            number=data['number'],
            serial=data['serial'],
            dl_number=data.get('dl_number', ''),
            worker_name=worker_name,
            arma_type=arma_type,
        )
    
    # Сохраняем запись в базе данных
    db.session.add(new_row)
    
    # Обновляем или создаём запись в ActiveUser
    try:
        selected_date = datetime.strptime(date, '%Y-%m-%d').date()
    except ValueError:
        return jsonify({"status": "error", "message": "Неверный формат даты"}), 400

    active_user = ActiveUser.query.filter_by(username=worker_name, date=selected_date).first()
    if active_user:
        active_user.record_count += 1
    else:
        active_user = ActiveUser(username=worker_name, date=selected_date, record_count=1)
        db.session.add(active_user)
    
    db.session.commit()
    
    # Отправляем сообщение в очередь для обновления клиентов
    update_queue.put(json.dumps({"type": "update", "date": date}))
    
    return jsonify({"status": "success"})




@app.route('/update_arma_type', methods=['POST'])
def update_arma_type():
    try:
        data = request.json
        date = data['date']
        serial = data['serial']
        arma_type = data['arma_type']
        
        row = TableRow.query.filter_by(date=date, serial=serial).first()
        if row:
            row.arma_type = arma_type
            db.session.commit()
            return jsonify({"status": "success"})
        else:
            return jsonify({"status": "error", "message": "Row not found"})
    except Exception as e:
        return jsonify({"status": "error", "message": str(e)})


@app.route('/update_number', methods=['POST'])
def update_number():
    try:
        data = request.json
        row_index = int(data['row_index'])
        new_value = data['new_value']
        date = data['date']
        
        rows = TableRow.query.filter_by(date=date).all()
        if 0 <= row_index < len(rows):
            row = rows[row_index]
            row.number = new_value
            db.session.commit()
            return jsonify({"status": "success"})
        return jsonify({"status": "error", "message": "Invalid row index"})
    except Exception as e:
        db.session.rollback()
        return jsonify({"status": "error", "message": str(e)})

@app.route('/pack_item', methods=['POST'])
def pack_item():
    try:
        data = request.json
        row_index = data['row_index']
        date = data['date']
        
        rows = TableRow.query.filter_by(date=date).all()
        if 0 <= row_index < len(rows):
            row = rows[row_index]
            pack_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            row.pack_time = pack_time
            db.session.commit()
            return jsonify({"status": "success", "pack_time": pack_time})
        
        return jsonify({"status": "error", "message": "Row not found"})
    except Exception as e:
        print(f"Error in pack_item: {str(e)}")
        return jsonify({"status": "error", "message": str(e)})



@app.route('/set_initial_dl', methods=['POST'])
def set_initial_dl():
    try:
        data = request.json
        if not data:
            return jsonify({"status": "error", "message": "No data received"})

        date = data.get('date')
        initial_number = data.get('initial_number')

        if not date or not initial_number:
            return jsonify({
                "status": "error", 
                "message": "Missing required fields",
                "received": {"date": date, "initial_number": initial_number}
            })

        try:
            initial_number = int(initial_number)
        except ValueError:
            return jsonify({
                "status": "error",
                "message": "Initial number must be an integer"
            })

        print(f"Setting initial DL for date {date}: {initial_number}")  # Отладочный вывод

        dl_counter = DLCounter.query.filter_by(date=date).first()
        if dl_counter:
            dl_counter.counter = initial_number
            print(f"Updated existing counter for {date}")  # Отладочный вывод
        else:
            dl_counter = DLCounter(date=date, counter=initial_number)
            db.session.add(dl_counter)
            print(f"Created new counter for {date}")  # Отладочный вывод

        db.session.commit()
        return jsonify({
            "status": "success",
            "message": f"DL counter set to {initial_number} for date {date}"
        })

    except Exception as e:
        print(f"Error in set_initial_dl: {str(e)}")  # Отладочный вывод
        db.session.rollback()
        return jsonify({
            "status": "error",
            "message": str(e)
        })



    
@app.route('/generate_dl', methods=['POST'])
def generate_dl():
    try:
        data = request.json
        row_index = data['row_index']
        date = data['date']
        
        # Получаем или создаем счетчик DL для данной даты
        dl_counter = DLCounter.query.filter_by(date=date).first()
        if not dl_counter:
            return jsonify({"status": "error", "message": "DL counter not initialized"})
        
        # Генерируем новый DL номер
        dl_number = f"DL{dl_counter.counter:06d}"
        
        # Находим и обновляем нужную запись
        row = TableRow.query.filter_by(date=date).offset(row_index).first()
        if row:
            row.dl_number = dl_number
            dl_counter.counter += 1  # Увеличиваем счетчик
            db.session.commit()
            return jsonify({"status": "success", "dl_number": dl_number})
        
        return jsonify({"status": "error", "message": "Row not found"})
    except Exception as e:
        return jsonify({"status": "error", "message": str(e)})

    

@app.route('/get_statistics')
def get_statistics():
    try:
        date = request.args.get('date')
        current_time = datetime.now()
        hour_ago = current_time - timedelta(hours=1)
        
        # Общее количество упакованных записей
        total_records = TableRow.query.filter_by(date=date).filter(TableRow.pack_time.isnot(None)).count()
        
        # Количество записей за последний час
        last_hour_records = TableRow.query.filter_by(date=date)\
            .filter(TableRow.pack_time.isnot(None))\
            .filter(TableRow.pack_time >= hour_ago.strftime("%Y-%m-%d %H:%M:%S"))\
            .count()
        
        return jsonify({
            "total_records": total_records,
            "last_hour_records": last_hour_records
        })
    except Exception as e:
        return jsonify({
            "total_records": 0,
            "last_hour_records": 0,
            "error": str(e)
        })


@app.route('/export_excel')
def export_excel():
    try:
        date = request.args.get('date')
        sort_column = request.args.get('sort_column')
        sort_order = request.args.get('sort_order')
        
        if not date:
            return jsonify({"error": "Date parameter is required"}), 400
        
        # Получаем все записи для выбранной даты
        query = TableRow.query.filter_by(date=date)
        
        # Применяем сортировку, если указана
        if sort_column and sort_order:
            column_mapping = {
                '0': TableRow.row_number,
                '1': TableRow.timestamp,
                '2': TableRow.number,
                '3': TableRow.serial,
                '4': TableRow.dl_number,
                '5': TableRow.worker_name,
                '6': TableRow.pack_time,  # Время упаковки
                '7': TableRow.arma_type,  # Тип АРМА
                '8': TableRow.last_graf
            }
            if sort_column in column_mapping:
                column = column_mapping[sort_column]
                
                # Явно приводим столбец "Номер" к целому числу для корректной сортировки
                if sort_column == '2':
                    column = cast(column, Integer)
                
                if sort_order == 'desc':
                    query = query.order_by(column.desc())
                else:
                    query = query.order_by(column.asc())
            else:
                return jsonify({"error": "Invalid sort column"}), 400
        
        rows = query.all()
        
        # Создаем DataFrame
        data = []
        for row in rows:
            # Безопасное преобразование номера
            try:
                number = int(row.number) if row.number and row.number.strip() else None
            except ValueError:
                number = None
            
            data.append({
                '№': row.row_number,
                'Время отправки': row.timestamp,
                'Номер': number,
                'Serial': row.serial,
                'Графа': row.dl_number,
                'Имя отправителя': row.worker_name,
                'Время упаковки': row.pack_time,  # Время упаковки
                'Тип АРМА': row.arma_type,  # Тип АРМА
                'last_graf': row.last_graf
            })
        
        df = pd.DataFrame(data)
        
        # Создаем Excel файл
        output = BytesIO()
        with pd.ExcelWriter(output, engine='xlsxwriter') as writer:
            df.to_excel(writer, index=False, sheet_name='Sheet1')
            
            # Настраиваем форматирование
            workbook = writer.book
            worksheet = writer.sheets['Sheet1']
            
            # Автоматическая ширина столбцов
            for idx, col in enumerate(df.columns):
                series = df[col]
                max_len = max(
                    series.astype(str).map(len).max(),
                    len(str(col))
                ) + 2
                worksheet.set_column(idx, idx, max_len)
                
                # Форматирование числовых столбцов
                if col in ['Номер', 'Графа']:
                    num_format = workbook.add_format({'num_format': '0'})
                    worksheet.set_column(idx, idx, max_len, num_format)
        
        output.seek(0)
        
        return send_file(
            output,
            mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            as_attachment=True,
            download_name=f'export_{date}.xlsx'
        )
    
    except Exception as e:
        print(f"Export error: {str(e)}")
        return jsonify({"error": str(e)}), 500



@app.route('/update_serial', methods=['POST'])
def update_serial():
    try:
        data = request.json
        row_index = int(data['row_index'])
        new_value = data['new_value']
        date = data['date']
        
        rows = TableRow.query.filter_by(date=date).all()
        if 0 <= row_index < len(rows):
            row = rows[row_index]
            row.serial = new_value
            db.session.commit()
            return jsonify({"status": "success"})
        return jsonify({"status": "error", "message": "Invalid row index"})
    except Exception as e:
        db.session.rollback()
        return jsonify({"status": "error", "message": str(e)})
    

def initialize_table_for_date(date):
    # Проверяем, существуют ли уже записи для этой даты
    existing = TableRow.query.filter_by(date=date).first()
    if not existing:
        # Создаем первую запись с заголовками
        first_row = TableRow(
            date=date,
            row_number="№",
            timestamp="Время отправки",
            number="Номер",
            serial="Serial",
            dl_number="Графа",
            worker_name="Имя отправителя",
            arma_type="Тип АРМА",
            pack_time="Время упаковки",
            last_graf="last_graf"
        )
        db.session.add(first_row)
        db.session.commit()

@app.route('/update_serial_arma', methods=['POST'])
def update_serial_arma():
    try:
        data = request.json
        date = data['date']
        serial = data['serial']
        arma_type = data['arma_type']
        
        # Создаем новую запись в таблице
        new_row = TableRow(
            date=date,
            serial=serial,
            arma_type=arma_type,
            timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        )
        
        db.session.add(new_row)
        db.session.commit()
        
        return jsonify({"status": "success"})
    except Exception as e:
        db.session.rollback()
        return jsonify({"status": "error", "message": str(e)}), 500




@app.route('/register_user', methods=['POST'])
def register_user():
    data = request.json
    worker_name = data.get('worker_name')
    
    if not worker_name:
        return jsonify({'status': 'error', 'message': 'Worker name is required'})
    
    today = datetime.now().date()
    
    # Проверяем, существует ли запись для пользователя на сегодня
    active_user = ActiveUser.query.filter_by(username=worker_name, date=today).first()
    if not active_user:
        active_user = ActiveUser(username=worker_name, date=today)
        db.session.add(active_user)
    
    db.session.commit()
    
    return jsonify({'status': 'success'})

@app.route('/get_active_users')
def get_active_users():
    today = datetime.now().date()
    active_users = ActiveUser.query.filter_by(date=today).all()
    
    return jsonify({user.username: user.record_count for user in active_users})


@app.route('/delete_row', methods=['POST'])
def delete_row():
    try:
        data = request.json
        row_id = data.get('id')
        
        if not row_id:
            return jsonify({'status': 'error', 'message': 'ID записи не предоставлен'}), 400
        
        row = TableRow.query.get(row_id)
        if not row:
            return jsonify({'status': 'error', 'message': 'Запись не найдена'}), 404
        
        db.session.delete(row)
        db.session.commit()
        
        # Отправляем сообщение в очередь для обновления клиентов
        update_queue.put(json.dumps({"type": "delete", "date": row.date, "id": row.id}))
        
        return jsonify({'status': 'success'})
    except Exception as e:
        db.session.rollback()
        return jsonify({'status': 'error', 'message': str(e)}), 500

@app.route('/', methods=['GET', 'POST'])
def main():
    if request.method == 'POST':
        worker = request.form.get('worker')
        name = request.form.get('name')
        
        # Сохраняем информацию о входе
        user = User.query.filter_by(username=name).first()
        if not user:
            user = User(username=name)
            db.session.add(user)
        
        login_history = LoginHistory(user=user)
        db.session.add(login_history)
        db.session.commit()
        
        resp = make_response(redirect('/welcome'))
        resp.set_cookie('worker', worker)
        resp.set_cookie('name', name)
        return resp

    return render_template('main.html')

@app.route('/get_current_dl', methods=['GET'])
def get_current_dl():
    date = request.args.get('date')
    if not date:
        return jsonify({"status": "error", "message": "Дата обязательна"}), 400
    
    dl_counter = DLCounter.query.filter_by(date=date).first()
    if dl_counter:
        return jsonify({"status": "success", "current_dl": dl_counter.counter})
    else:
        return jsonify({"status": "error", "message": "Счетчик DL не инициализирован для этой даты"}), 404


@app.route('/welcome')
def welcome():
    worker = request.cookies.get('worker')
    name = request.cookies.get('name')
    
    if not worker or not name:
        return redirect('/')
    
    return render_template('welcome.html', name=name, worker=worker, table=table)



@app.route('/logout')
def logout():
    resp = make_response(redirect('/'))
    resp.delete_cookie('worker')
    resp.delete_cookie('name')
    return resp




if __name__ == '__main__':
    with app.app_context():
        db.create_all()

    app.debug = True
    logging.disable(logging.CRITICAL)
    app.run(host='192.168.0.158', port=80)
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 getArmaFilter() {
    return $('#armaFilter').is(':checked') ? 'RPA' : '';
}

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(),
            filter_arma: getArmaFilter()
             },
            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, rowData) {
    console.log(`Updating cell: index=${cellIndex}, value=${newValue}, rowIndex=${rowIndex}`);
    
    if (cellIndex === 4) { // Столбец "Графа"
        if (rowData.arma_type === 'RPA') {
            if (newValue && newValue.startsWith('DL')) {
                cell.text(newValue)
                    .addClass('clickable')
                    .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 (newValue && newValue.startsWith('DL')) {
                cell.text(newValue)
                    .addClass('clickable')
                    .css('cursor', 'pointer')
                    .off('click')
                    .on('click', function() {
                        copyToClipboard(newValue);
                        $(this).addClass('copied');
                        setTimeout(() => {
                            $(this).removeClass('copied');
                        }, 200);
                    });
            } else {
                cell.text(newValue || '');
            }
        }
    } 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)
                    .addClass('clickable')
                    .css('cursor', 'pointer')
                    .off('click') // Удаляем предыдущие обработчики
                    .on('click', function() {
                        copyToClipboard(response.dl_number);
                        $(this).addClass('copied');
                        setTimeout(() => {
                            $(this).removeClass('copied');
                        }, 200);
                    });
                
                // Добавляем кнопку упаковки
                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>').attr('data-id', rowData.id); // Присваиваем атрибут data-id

    // Проверяем наличие времени упаковки
    if (rowData.pack_time && rowData.pack_time !== "Время упаковки") {
        tr.addClass('packed-row');
    }

    // Добавляем все ячейки
    tr.append($('<td>').text(rowData.row_number));
    tr.append($('<td>').text(rowData.timestamp));
    tr.append($('<td>').text(rowData.number));
    tr.append($('<td>').text(rowData.serial));

    // Обработка ячейки "Графа" (cellIndex 4)
    var dlCell = $('<td>');
    if (rowData.arma_type === 'RPA') {
        // Если dl_number отсутствует или не начинается с 'DL', показываем кнопку "Генерировать"
        if (!rowData.dl_number || !rowData.dl_number.startsWith('DL')) {
            dlCell.html('<button class="generate-button">Генерировать</button>')
                .find('button')
                .click(function() {
                    generateDL(tr.index());
                });
        } else {
            // Если dl_number присутствует и начинается с 'DL', делаем его кликабельным для копирования
            dlCell.text(rowData.dl_number)
                .addClass('clickable')
                .css('cursor', 'pointer');
        }
    } else {
        // Для других типов АРМА отображаем просто dl_number или пустую ячейку
        if (rowData.dl_number && rowData.dl_number.startsWith('DL')) {
            dlCell.text(rowData.dl_number)
                .addClass('clickable')
                .css('cursor', 'pointer');
        } else {
            dlCell.text(rowData.dl_number || '');
        }
    }

    tr.append(dlCell);

    tr.append($('<td>').text(rowData.worker_name));
    tr.append($('<td>').text(rowData.pack_time));
    tr.append($('<td>').text(rowData.arma_type));
    tr.append($('<td>').text(rowData.last_graf));

    // Добавляем ячейку с кнопкой удаления
    var actionTd = $('<td>');
    var deleteButton = $('<button>').text('Удалить').addClass('delete-button');
    deleteButton.click(function() {
        deleteRow(rowData.id);
    });
    actionTd.append(deleteButton);
    tr.append(actionTd);

    tableBody.append(tr);

    // Дополнительная проверка после добавления строки
    setTimeout(() => {
        const dlValue = dlCell.text().trim();
        const packCell = tr.find('td').eq(6); // Столбец "Время упаковки"
        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);
}

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();
    loadCurrentDL();
    // Обработчик сортировки
    $('.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();
        }
    });
    $('#armaFilter').on('change', function() {
        updateTable();
    });
});

// Обработчик изменения даты
$('#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('Точное совпадение номера не найдено');
    }
}

function deleteRow(rowId) {
    $.ajax({
        url: '/delete_row',
        type: 'POST',
        contentType: 'application/json',
        data: JSON.stringify({ id: rowId }),
        success: function(response) {
            if (response.status === 'success') {
                // Удаляем строку из таблицы без уведомления
                $(`tr[data-id='${rowId}']`).remove();
            } else {
                // Логируем ошибку в консоль вместо alert
                console.error('Ошибка при удалении записи:', response.message);
            }
        },
        error: function(xhr, status, error) {
            // Логируем ошибку в консоль вместо alert
            console.error('Произошла ошибка при удалении записи:', error);
        }
    });
}

function loadCurrentDL() {
    const selectedDate = getSelectedDate();
    $.ajax({
        url: '/get_current_dl',
        type: 'GET',
        data: { date: selectedDate },
        success: function(response) {
            if (response.status === 'success') {
                $('#initial-dl').val(response.current_dl);
            } else {
                $('#initial-dl').val(''); // Можно установить значение по умолчанию или показать сообщение
                console.warn(response.message);
            }
        },
        error: function(xhr, status, error) {
            console.error('Ошибка при получении текущего DL:', error);
            $('#initial-dl').val('');
        }
    });
}function updateStatistics() {
    const selectedDate = getSelectedDate();
    $.ajax({
        url: '/get_statistics',
        type: 'GET',
        data: { date: selectedDate },
        success: function(response) {
            $('#totalRecords').text(response.total_records);
            $('#lastHourRecords').text(response.last_hour_records);
        },
        error: function(xhr, status, error) {
            console.error("Error fetching statistics:", error);
            $('#totalRecords').text('Error');
            $('#lastHourRecords').text('Error');
        }
    });
}

function setInitialDL() {
    const initialNumber = document.getElementById('initial-dl').value;
    const selectedDate = getSelectedDate();
    
    console.log("Setting initial DL:", {
        initialNumber: initialNumber,
        date: selectedDate
    });

    if (!initialNumber) {
        alert('Пожалуйста, введите начальное значение DL');
        return;
    }

    $.ajax({
        url: '/set_initial_dl',
        type: 'POST',
        contentType: 'application/json',
        data: JSON.stringify({
            initial_number: initialNumber,
            date: selectedDate
        }),
        success: function(response) {
            console.log("Server response:", response);
            if (response.status === 'success') {
                alert('Начальное значение DL успешно установлено');
                loadCurrentDL(); // Обновляем отображение текущего DL
            } else {
                alert('Ошибка: ' + (response.message || 'Неизвестная ошибка'));
            }
        },
        error: function(xhr, status, error) {
            console.error("AJAX error:", {xhr, status, error});
            alert('Ошибка при отправке данных: ' + error);
        }
    });
}