# Исходный код components.table_component

    """Модуль компонента таблицы. Содержит класс для работы с табличными данными."""

    from datetime import datetime
    from playwright.sync_api import Page, expect, Locator
    from tools.logger import get_logger
    from components.base_component import BaseComponent

    logger = get_logger("TABLE")

    class TableComponent(BaseComponent):
        """Компонент таблицы. Предоставляет методы для работы с табличными данными."""

        def __init__(self, page: Page):
            """Инициализирует компонент таблицы.

            Args:
                page: Экземпляр страницы Playwright.
            """

            super().__init__(page)

        # Действия:

        def click_arrow_button(self, table_locator: str | Locator, index: int) -> None:
            """ Нажатие кнопки-стрелочки вверх/вниз в ячейке заголовка таблицы

            Args:
                table_locator: Локатор таблицы.
                index: Индекс ячейки в заголовке.
            """

            arrow_button = self.get_header_cell_button(table_locator, index)
            assert arrow_button.is_enabled(), f"Arrow button is missing in {index} header cell"
            arrow_button.click()

        def datetime2timestamp(self, date_string: str) -> float|None:
            """ Конвертация строкового представления даты и времени в Unix timestamp
            Args:
                date_string: Строка с датой и временем в формате d.m.Y H:M:S.

            Returns:
                float: Unix timestamp.
                None: конвертация невозможна
            """

            # Формат, соответствующий строке с датой и временем
            format_string = "%d.%m.%Y %H:%M:%S"

            try:
                date_object = datetime.strptime(date_string, format_string)
                return date_object.timestamp()
            except ValueError:
                return None

        def get_arrow_button_state(self, table_locator: str | Locator, index: int) -> str:
            """ Получение состояния кнопки-стрелочки вверх/вниз в ячейке заголовка таблицы

            Args:
                table_locator: Локатор таблицы.
                index: Индекс ячейки в заголовке.

            Returns:
                up, если это стрелочка вверх. down, если это стрелочка вниз.
            """

            arrow_button = self.get_header_cell_button(table_locator, index)
            assert arrow_button.is_enabled(), f"Arrow button is missing in {index} header cell"

            state = arrow_button.inner_text()
            if state == "keyboard_arrow_up":
                return "up"
            elif state == "keyboard_arrow_down":
                return "down"
            else:
                assert False, f"Got unsupported arrow state: {state}"

        def get_header_cell_button(self, table_locator: str | Locator, index: int) -> Locator:
            """ Поиск кнопки в ячейке заголовка таблицы

            Args:
                table_locator: Локатор таблицы.
                index: Индекс ячейки в заголовке.

            Returns:
                Локатор строки кнопки.

            Raises:
                AssertionError: Если индекс вне диапазона.
            """

            table = self.get_locator(table_locator)
            header_cells_count = table.locator("//thead/tr/th").count()
            assert index in range(header_cells_count), "Header cell index is out of range"
            return table.locator("//thead/tr/th").nth(index).get_by_role("button")

        def get_row_locator(self, table_locator: str | Locator, row_index: int) -> Locator | None:
            """Возвращает локатор строки по индексу.

            Args:
                table_locator: Локатор таблицы.
                row_index: Индекс строки.

            Returns:
                Локатор строки или None, если индекс вне диапазона.
            """

            table = self.get_locator(table_locator)

            rows = table.locator("//tbody/tr")

            if row_index in range(rows.count()):
                return rows.nth(row_index)
            else:
                return None

        def get_rows_count(self, locator: str | Locator) -> int:
            """Возвращает количество строк в таблице (без заголовка).

            Returns:
                int: Количество строк с данными.

            Raises:
                AssertionError: Если таблица пуста.
            """

            table_content = self.read(locator)
            rows_count = len(table_content)

            if rows_count == 0:
                assert False, "The contents of the table are missing"

            return rows_count - 1

        def read(self, locator: str | Locator) -> list[list[str]]:
            """Читает данные таблицы, включая заголовки.

            Args:
                locator: Локатор таблицы.

            Returns:
                Двумерный список с данными таблицы.
            """

            table_data = []
            table = self.get_locator(locator)

            # Чтение заголовка таблицы
            header_cells = table.locator("//thead/tr")
            header_cell_text = header_cells.nth(0).inner_text()
            header_data = header_cell_text.split('\n')
            table_data.append(header_data)

            # Чтение ячеек таблицы
            rows = table.locator("//tbody/tr")
            for i in range(rows.count()):
                row = rows.nth(i)
                cells = row.locator("td")
                row_data = []
                for j in range(cells.count()):
                    cell_text = cells.nth(j).inner_text()
                    row_data.append(cell_text)
                table_data.append(row_data)

            return table_data

        # Проверки:

        def check_table_headers(self, actual_headers, expected_headers) -> None:
            """ Проверка соответствия заголовка таблицы ожидаемому"""

            is_equals = True

            arrow_state = ["keyboard_arrow_down", "keyboard_arrow_up"]

            for item in actual_headers:
                item = item.strip()

                if item in arrow_state:
                    continue

                if item not in expected_headers:
                    is_equals = False

            assert is_equals, \
                f"Expected events table headers {expected_headers} are not equal {actual_headers}"

        def check_content(self,
                        locator: str | Locator,
                        expected_headers: list[str],
                        check_table_not_empty: bool = True) -> None:
            """Проверяет содержимое таблицы.

            Проверяет заголовки и наличие данных в таблице.

            Args:
                locator: Локатор таблицы.
                expected_headers: Список ожидаемых заголовков таблицы.
                check_table_not_empty: Флаг проверки, что таблица не пустая.
                                    По умолчанию True.

            Raises:
                AssertionError: Если таблица пуста (при check_table_not_empty=True)
                            или заголовки неверны.
            """

            table_content = self.read(locator)

            if len(table_content) == 0:
                assert False, "The contents of the table are missing"

            # Проверка заголовков таблицы
            self.check_table_headers(table_content[0], expected_headers)

            # Проверка наличия данных в таблице
            if len(table_content) == 1:
                if check_table_not_empty:
                    assert False, "Table body is missing"
                else:
                    logger.info("Таблица пустая (не содержит строк с данными)")

        def check_column_descending_order(self,
                                             locator: str | Locator,
                                             index: int,
                                             convert2timestamp=False) -> bool:
            """Проверка, что заданный столбец таблицы упорядочен по убыванию.

            Args:
                locator: Локатор таблицы.
                index: Индекс столбца.
                convert2timestamp: Конвертировать строковое представление даты и времени в Unix timestamp

            Returns:
                True, если столбец таблицы упорядочен по убыванию. Иначе: False
            """

            table_content = self.read(locator)

            if len(table_content) == 0:
                assert False, "The contents of the table are missing"

            del table_content[0]

            assert index in range(len(table_content[0])), \
                                "Column index is out of range"
            column = []
            for i in range(len(table_content)):
                if convert2timestamp:
                    timestamp = self.datetime2timestamp(table_content[i][index])
                    assert timestamp, f"Error conversation to timestamp for {table_content[i][index]}"
                    column.append(timestamp)
                else:
                    column.append(table_content[i][index])

            return all(column[i] >= column[i+1] for i in range(len(column) - 1))

        def check_first_row_visibility(self, locator: str | Locator) -> None:
            """Проверяет видимость первой строки таблицы.

            Args:
                locator: Локатор таблицы.
            """

            table = self.get_locator(locator)
            first_row = table.locator("//tbody/tr").first
            expect(first_row).to_be_visible(), "The first table row is not visible"

        def check_last_row_visibility(self, locator: str | Locator) -> None:
            """Проверяет видимость последней строки таблицы.

            Args:
                locator: Локатор таблицы.
            """

            table = self.get_locator(locator)
            last_row = table.locator("//tbody/tr").last
            expect(last_row).to_be_visible(), "The last table row is not visible"

        def check_row_highlighting(self, locator: str | Locator, row_index: int) -> None:
            """Проверяет изменение цвета строки при наведении.

            Args:
                locator: Локатор таблицы.
                row_index: Индекс проверяемой строки.
            """

            table = self.get_locator(locator)
            row = table.locator("//tbody/tr").nth(row_index)

            row.scroll_into_view_if_needed()
            hover_element = row.locator(".body-row-hover")
            initial_color = hover_element.evaluate("el => window.getComputedStyle(el).backgroundColor")

            row.hover()
            self.page.wait_for_timeout(300)

            new_color = hover_element.evaluate("el => window.getComputedStyle(el).backgroundColor")
            assert initial_color != new_color, "Color of row did not change when hovering the cursor"