# gui/main_wind/w.py """ Главное окно приложения PyQt6 с поддержкой авторизации """ import sys import requests from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QListWidget, QListWidgetItem, QMessageBox, QFrame, QStackedWidget, QMenuBar, QMenu, QStatusBar, QToolBar) from PyQt6.QtCore import Qt, pyqtSignal from PyQt6.QtGui import QFont, QPixmap, QIcon, QAction from .partner_form import PartnerForm from .sales_history import SalesHistoryWindow from .material_calculator import MaterialCalculatorWindow class PartnerCard(QFrame): """Карточка партнера для отображения в списке""" partner_clicked = pyqtSignal(dict) def __init__(self, partner_data): super().__init__() self.partner_data = partner_data self.setup_ui() def setup_ui(self): self.setFrameStyle(QFrame.Shape.StyledPanel) self.setStyleSheet(""" PartnerCard { background-color: white; border: 1px solid #ddd; border-radius: 8px; padding: 12px; margin: 4px; } PartnerCard:hover { background-color: #f5f5f5; border-color: #007acc; } """) layout = QVBoxLayout() layout.setContentsMargins(8, 8, 8, 8) layout.setSpacing(4) # Заголовок с типом и названием header_layout = QHBoxLayout() header_layout.setSpacing(4) type_label = QLabel(f"{self.partner_data.get('partner_type', 'Тип не указан')} |") type_label.setStyleSheet("color: #666; font-weight: bold;") name_label = QLabel(self.partner_data['company_name']) name_label.setStyleSheet("font-weight: bold; font-size: 14px;") name_label.setWordWrap(True) # Безопасное преобразование рейтинга rating_value = self.partner_data.get('rating', 0) if isinstance(rating_value, float): rating_value = int(rating_value) rating_label = QLabel(f"{rating_value}%") rating_label.setStyleSheet("color: #007acc; font-weight: bold;") header_layout.addWidget(type_label) header_layout.addWidget(name_label) header_layout.addStretch() header_layout.addWidget(rating_label) # Информация о директоре QLabel(self.partner_data.get('director_name', 'Директор не указан')) director_label.setStyleSheet("color: #444;") # Контактная информация phone_label = QLabel(self.partner_data.get('phone', 'Телефон не указан')) phone_label.setStyleSheet("color: #666;") layout.addLayout(header_layout) layout.addWidget(director_label) layout.addWidget(phone_label) self.setLayout(layout) def mousePressEvent(self, event): """Обработка клика на карточке""" if event.button() == Qt.MouseButton.LeftButton: self.partner_clicked.emit(self.partner_data) class MainWindow(QMainWindow): """Главное окно приложения с поддержкой авторизации""" def __init__(self, user_data): super().__init__() self.user_data = user_data self.current_partner = None self.orders_panel = None self.auth = user_data.get('auth') self.setup_ui() self.load_partners() def setup_ui(self): """Настройка интерфейса главного окна""" self.setWindowTitle(f"MasterPol - Система управления партнерами") self.setGeometry(100, 100, 1200, 700) # Установка иконки приложения try: self.setWindowIcon(QIcon("resources/icon.png")) except: pass # Создание меню self.create_menu() # Создание тулбара self.create_toolbar() # Создание статусной строки self.create_statusbar() # Центральный виджет central_widget = QWidget() self.setCentralWidget(central_widget) main_layout = QHBoxLayout() main_layout.setContentsMargins(0, 0, 0, 0) # Левая панель - список партнеров left_panel = self.create_partners_panel() main_layout.addWidget(left_panel, 1) # Правая панель - детальная информация self.right_panel = self.create_details_panel() main_layout.addWidget(self.right_panel, 2) central_widget.setLayout(main_layout) def create_menu(self): """Создание меню приложения""" menubar = self.menuBar() # Меню Файл file_menu = menubar.addMenu('Файл') refresh_action = QAction('Обновить', self) refresh_action.setShortcut('F5') refresh_action.triggered.connect(self.load_partners) file_menu.addAction(refresh_action) file_menu.addSeparator() logout_action = QAction('Выход', self) logout_action.setShortcut('Ctrl+Q') logout_action.triggered.connect(self.logout) file_menu.addAction(logout_action) # Меню Сервис service_menu = menubar.addMenu('Сервис') calc_action = QAction('Калькулятор материалов', self) calc_action.triggered.connect(self.show_material_calculator) service_menu.addAction(calc_action) # Меню Справка help_menu = menubar.addMenu('Справка') about_action = QAction('О программе', self) about_action.triggered.connect(self.show_about) help_menu.addAction(about_action) def create_toolbar(self): """Создание панели инструментов""" toolbar = QToolBar("Основные инструменты") self.addToolBar(toolbar) refresh_action = QAction('Обновить', self) refresh_action.triggered.connect(self.load_partners) toolbar.addAction(refresh_action) toolbar.addSeparator() add_partner_action = QAction('Добавить партнера', self) add_partner_action.triggered.connect(self.show_add_partner_form) toolbar.addAction(add_partner_action) def create_statusbar(self): """Создание статусной строки""" statusbar = self.statusBar() user_info = f"Пользователь: {self.user_data.get('full_name', 'Неизвестно')}" statusbar.showMessage(user_info) def create_partners_panel(self): """Создание панели списка партнеров""" panel = QWidget() panel.setMaximumWidth(400) layout = QVBoxLayout() layout.setContentsMargins(10, 10, 10, 10) layout.setSpacing(10) # Заголовок title = QLabel("Партнеры") title.setFont(QFont("Arial", 16, QFont.Weight.Bold)) title.setAlignment(Qt.AlignmentFlag.AlignCenter) title.setStyleSheet("padding: 10px;") layout.addWidget(title) # Панель управления control_layout = QHBoxLayout() control_layout.setSpacing(10) self.add_button = QPushButton("Добавить партнера") self.add_button.clicked.connect(self.show_add_partner_form) self.add_button.setStyleSheet(""" QPushButton { background-color: #007acc; color: white; border: none; padding: 8px 16px; border-radius: 4px; font-weight: bold; } QPushButton:hover { background-color: #005a9e; } """) self.refresh_button = QPushButton("Обновить") self.refresh_button.clicked.connect(self.load_partners) self.refresh_button.setStyleSheet(""" QPushButton { background-color: #6c757d; color: white; border: none; padding: 8px 16px; border-radius: 4px; font-weight: bold; } QPushButton:hover { background-color: #545b62; } """) control_layout.addWidget(self.add_button) control_layout.addWidget(self.refresh_button) control_layout.addStretch() layout.addLayout(control_layout) # Список партнеров self.partners_list = QListWidget() self.partners_list.setStyleSheet(""" QListWidget { border: 1px solid #ddd; border-radius: 4px; background-color: white; outline: none; } QListWidget::item { border: none; padding: 0px; } QListWidget::item:selected { background-color: transparent; } """) layout.addWidget(self.partners_list) # Кнопка расчета материалов self.calc_button = QPushButton("Калькулятор материалов") self.calc_button.clicked.connect(self.show_material_calculator) self.calc_button.setStyleSheet(""" QPushButton { background-color: #17a2b8; color: white; border: none; padding: 8px 16px; border-radius: 4px; font-weight: bold; } QPushButton:hover { background-color: #138496; } """) layout.addWidget(self.calc_button) panel.setLayout(layout) return panel def create_details_panel(self): """Создание панели детальной информации""" panel = QWidget() layout = QVBoxLayout() layout.setContentsMargins(10, 10, 10, 10) layout.setSpacing(10) # Заголовок детальной информации self.details_title = QLabel("Выберите партнера") self.details_title.setFont(QFont("Arial", 14, QFont.Weight.Bold)) self.details_title.setAlignment(Qt.AlignmentFlag.AlignCenter) self.details_title.setStyleSheet("padding: 10px;") layout.addWidget(self.details_title) # Детальная информация о партнере - создаем пустой frame self.details_frame = QFrame() self.details_frame.setFrameStyle(QFrame.Shape.StyledPanel) self.details_frame.setStyleSheet(""" QFrame { background-color: #f9f9f9; border: 1px solid #ddd; border-radius: 5px; padding: 15px; } """) self.details_layout = QVBoxLayout() self.details_layout.setSpacing(8) self.details_frame.setLayout(self.details_layout) self.details_frame.hide() layout.addWidget(self.details_frame) # Кнопки управления выбранным партнером self.control_buttons = QWidget() buttons_layout = QHBoxLayout() buttons_layout.setSpacing(10) self.edit_button = QPushButton("Редактировать") self.edit_button.clicked.connect(self.edit_partner) self.edit_button.setStyleSheet(""" QPushButton { background-color: #007acc; color: white; border: none; padding: 8px 16px; border-radius: 4px; font-weight: bold; } QPushButton:hover { background-color: #005a9e; } """) self.edit_button.hide() self.sales_button = QPushButton("История продаж") self.sales_button.clicked.connect(self.show_sales_history) self.sales_button.setStyleSheet(""" QPushButton { background-color: #28a745; color: white; border: none; padding: 8px 16px; border-radius: 4px; font-weight: bold; } QPushButton:hover { background-color: #218838; } """) self.sales_button.hide() self.discount_button = QPushButton("Расчет скидки") self.discount_button.clicked.connect(self.calculate_discount) self.discount_button.setStyleSheet(""" QPushButton { background-color: #ffc107; color: black; border: none; padding: 8px 16px; border-radius: 4px; font-weight: bold; } QPushButton:hover { background-color: #e0a800; } """) self.discount_button.hide() buttons_layout.addWidget(self.edit_button) buttons_layout.addWidget(self.sales_button) buttons_layout.addWidget(self.discount_button) buttons_layout.addStretch() self.control_buttons.setLayout(buttons_layout) layout.addWidget(self.control_buttons) # Добавляем растягивающийся элемент в конец layout.addStretch() panel.setLayout(layout) return panel def load_partners(self): """Загрузка списка партнеров из API с авторизацией""" try: response = requests.get( "http://localhost:8000/api/v1/partners", auth=self.auth, timeout=10 ) if response.status_code == 200: self.partners_list.clear() partners = response.json() for partner in partners: item = QListWidgetItem() card = PartnerCard(partner) card.partner_clicked.connect(self.show_partner_details) # Устанавливаем фиксированный размер для элемента item.setSizeHint(card.sizeHint()) self.partners_list.addItem(item) self.partners_list.setItemWidget(item, card) # Сбрасываем выделение self.partners_list.clearSelection() self.current_partner = None self.details_title.setText("Выберите партнера") self.details_frame.hide() self.edit_button.hide() self.sales_button.hide() self.discount_button.hide() elif response.status_code == 401: QMessageBox.warning(self, "Ошибка авторизации", "Сессия истекла. Пожалуйста, войдите снова.") self.logout() else: QMessageBox.warning(self, "Ошибка", "Не удалось загрузить партнеров") except requests.exceptions.ConnectionError: QMessageBox.critical(self, "Ошибка", "Не удалось подключиться к серверу") except Exception as e: QMessageBox.warning(self, "Ошибка", f"Не удалось загрузить партнеров: {str(e)}") def show_partner_details(self, partner_data): """Отображение детальной информации о партнере""" self.current_partner = partner_data self.details_title.setText(partner_data['company_name']) # Создаем новый виджет для деталей вместо очистки layout new_details_frame = QFrame() new_details_frame.setFrameStyle(QFrame.Shape.StyledPanel) new_details_frame.setStyleSheet(""" QFrame { background-color: #f9f9f9; border: 1px solid #ddd; border-radius: 5px; padding: 15px; } """) new_details_layout = QVBoxLayout() new_details_layout.setSpacing(8) # Добавляем новую информацию details = [ ("Тип:", partner_data.get('partner_type', 'Не указан')), ("ИНН:", partner_data.get('inn', 'Не указан')), ("Директор:", partner_data.get('director_name', 'Не указан')), ("Телефон:", partner_data.get('phone', 'Не указан')), ("Email:", partner_data.get('email', 'Не указан')), ("Рейтинг:", str(partner_data.get('rating', 0))), ("Адрес:", partner_data.get('legal_address', 'Не указан')), ("Регионы:", partner_data.get('sales_locations', 'Не указан')) ] # ЗАМЕНИТЕ этот блок кода в методе show_partner_details: for label, value in details: row_widget = QWidget() row_layout = QHBoxLayout(row_widget) row_layout.setContentsMargins(0, 2, 0, 2) label_widget = QLabel(label) label_widget.setStyleSheet("font-weight: bold; min-width: 100px;") label_widget.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop) value_widget = QLabel(str(value)) value_widget.setWordWrap(True) value_widget.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop) row_layout.addWidget(label_widget) row_layout.addWidget(value_widget) row_layout.addStretch() new_details_layout.addWidget(row_widget) # НА этот исправленный вариант: for label, value in details: # Создаем контейнер для строки row_container = QWidget() row_container.setFixedHeight(30) # Фиксированная высота для каждой строки row_layout = QHBoxLayout(row_container) row_layout.setContentsMargins(5, 0, 5, 0) row_layout.setSpacing(10) # Лейбл (название поля) label_widget = QLabel(label) label_widget.setStyleSheet(""" QLabel { font-weight: bold; color: #333; min-width: 120px; max-width: 120px; background-color: transparent; } """) label_widget.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter) # Значение value_widget = QLabel(str(value)) value_widget.setStyleSheet(""" QLabel { color: #555; background-color: transparent; border: none; } """) value_widget.setWordWrap(True) value_widget.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter) row_layout.addWidget(label_widget) row_layout.addWidget(value_widget) row_layout.addStretch() new_details_layout.addWidget(row_container) new_details_frame.setLayout(new_details_layout) # Заменяем старый details_frame на новый old_frame = self.details_frame layout = self.right_panel.layout() layout.replaceWidget(old_frame, new_details_frame) old_frame.deleteLater() self.details_frame = new_details_frame self.details_layout = new_details_layout self.details_frame.show() self.edit_button.show() self.sales_button.show() self.discount_button.show() def show_add_partner_form(self): """Открытие формы добавления партнера""" form = PartnerForm(self, auth=self.auth) form.partner_saved.connect(self.load_partners) form.exec() def edit_partner(self): """Редактирование выбранного партнера""" if self.current_partner: form = PartnerForm(self, self.current_partner, auth=self.auth) form.partner_saved.connect(self.load_partners) form.exec() def show_sales_history(self): """Открытие истории продаж партнера""" if self.current_partner: sales_window = SalesHistoryWindow(self.current_partner, self, auth=self.auth) sales_window.exec() def calculate_discount(self): """Расчет скидки для партнера с авторизацией""" if self.current_partner: try: response = requests.get( f"http://localhost:8000/api/v1/partners/{self.current_partner['partner_id']}/discount", auth=self.auth, timeout=10 ) if response.status_code == 200: discount_data = response.json() QMessageBox.information( self, "Расчет скидки", f"Партнер: {self.current_partner['company_name']}\n" f"Общие продажи: {discount_data['total_sales']}\n" f"Скидка: {discount_data['discount_percent']}%" ) elif response.status_code == 401: QMessageBox.warning(self, "Ошибка авторизации", "Сессия истекла. Пожалуйста, войдите снова.") self.logout() except Exception as e: QMessageBox.warning(self, "Ошибка", f"Не удалось рассчитать скидку: {str(e)}") def show_material_calculator(self): """Открытие калькулятора материалов""" calculator = MaterialCalculatorWindow(self, auth=self.auth) calculator.exec() def logout(self): """Выход из системы""" reply = QMessageBox.question( self, "Подтверждение выхода", "Вы уверены, что хотите выйти из системы?", QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, QMessageBox.StandardButton.No ) if reply == QMessageBox.StandardButton.Yes: self.close() # Здесь можно добавить вызов окна авторизации # или перезапуск приложения def show_about(self): """Показать информацию о программе""" QMessageBox.about( self, "О программе MasterPol", "MasterPol - Система управления партнерами\n\n" "Версия: 1.0.0\n" "Разработчик: Команда MasterPol\n\n" "Система предназначена для управления партнерами,\n" "учета продаж и расчета бизнес-показателей." )