mobiles_dataset/main_window.py
2025-06-07 12:23:39 +03:00

599 lines
No EOL
26 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ui/main_window.py
import sys
from PyQt6.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QTableWidget, QTableWidgetItem, QPushButton, QTabWidget,
QLabel, QLineEdit, QComboBox, QSpinBox, QMessageBox,
QDialog, QFormLayout, QDialogButtonBox, QHeaderView,
QToolBar, QStatusBar, QGroupBox, QTextEdit, QInputDialog
)
from PyQt6.QtCore import Qt, QTimer, pyqtSignal
from PyQt6.QtGui import QAction, QIcon, QFont
from typing import Optional, Dict, Any
import logging
# Импортируем наш модуль БД
from database import Database
logger = logging.getLogger(__name__)
# Маппинг валют по регионам
CURRENCY_MAP = {
'Pakistan': ('PKR', ''),
'India': ('INR', ''),
'China': ('CNY', '¥'),
'USA': ('USD', '$'),
'Dubai': ('AED', 'د.إ')
}
class ModelDialog(QDialog):
"""Диалог для добавления/редактирования модели"""
def __init__(self, parent=None, model_data=None):
super().__init__(parent)
self.model_data = model_data
self.db = Database()
self.init_ui()
def init_ui(self):
self.setWindowTitle("Добавить модель" if not self.model_data else "Редактировать модель")
self.setModal(True)
self.setMinimumWidth(500)
layout = QFormLayout()
# Поля формы
self.company_combo = QComboBox()
companies = self.db.get_all_companies()
for company in companies:
self.company_combo.addItem(company['company_name'], company['company_id'])
self.model_name_edit = QLineEdit()
self.weight_edit = QLineEdit()
self.ram_edit = QLineEdit()
self.front_camera_edit = QLineEdit()
self.back_camera_edit = QLineEdit()
self.processor_edit = QLineEdit()
self.battery_edit = QLineEdit()
self.screen_edit = QLineEdit()
self.year_spin = QSpinBox()
self.year_spin.setRange(2000, 2030)
self.year_spin.setValue(2024)
# Добавляем поля в форму
layout.addRow("Компания:", self.company_combo)
layout.addRow("Название модели:", self.model_name_edit)
layout.addRow("Вес:", self.weight_edit)
layout.addRow("RAM:", self.ram_edit)
layout.addRow("Фронтальная камера:", self.front_camera_edit)
layout.addRow("Основная камера:", self.back_camera_edit)
layout.addRow("Процессор:", self.processor_edit)
layout.addRow("Батарея:", self.battery_edit)
layout.addRow("Размер экрана:", self.screen_edit)
layout.addRow("Год выпуска:", self.year_spin)
# Заполняем данные при редактировании
if self.model_data:
self.model_name_edit.setText(self.model_data.get('model_name', ''))
self.weight_edit.setText(self.model_data.get('mobile_weight', ''))
self.ram_edit.setText(self.model_data.get('ram', ''))
self.front_camera_edit.setText(self.model_data.get('front_camera', ''))
self.back_camera_edit.setText(self.model_data.get('back_camera', ''))
self.processor_edit.setText(self.model_data.get('processor_name', ''))
self.battery_edit.setText(self.model_data.get('battery_capacity', ''))
self.screen_edit.setText(self.model_data.get('screen_size', ''))
if self.model_data.get('launched_year'):
self.year_spin.setValue(self.model_data['launched_year'])
# Устанавливаем компанию
for i in range(self.company_combo.count()):
if self.company_combo.itemData(i) == self.model_data.get('company_id'):
self.company_combo.setCurrentIndex(i)
break
# Кнопки
buttons = QDialogButtonBox(
QDialogButtonBox.StandardButton.Ok |
QDialogButtonBox.StandardButton.Cancel
)
buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject)
layout.addRow(buttons)
self.setLayout(layout)
def get_data(self) -> Dict[str, Any]:
"""Получение данных из формы"""
return {
'company_id': self.company_combo.currentData(),
'model_name': self.model_name_edit.text(),
'mobile_weight': self.weight_edit.text() or None,
'ram': self.ram_edit.text() or None,
'front_camera': self.front_camera_edit.text() or None,
'back_camera': self.back_camera_edit.text() or None,
'processor_name': self.processor_edit.text() or None,
'battery_capacity': self.battery_edit.text() or None,
'screen_size': self.screen_edit.text() or None,
'launched_year': self.year_spin.value()
}
class PriceDialog(QDialog):
"""Диалог для управления ценами модели"""
def __init__(self, parent=None, model_id=None, model_name=""):
super().__init__(parent)
self.model_id = model_id
self.model_name = model_name
self.db = Database()
self.init_ui()
self.load_prices()
def init_ui(self):
self.setWindowTitle(f"Цены для: {self.model_name}")
self.setModal(True)
self.setMinimumSize(600, 400)
layout = QVBoxLayout()
# Таблица цен
self.prices_table = QTableWidget()
self.prices_table.setColumnCount(4)
self.prices_table.setHorizontalHeaderLabels(["Регион", "Цена", "Валюта", "Действия"])
self.prices_table.setSortingEnabled(True)
# Настройка размеров столбцов
header = self.prices_table.horizontalHeader()
header.setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents)
header.setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents)
header.setSectionResizeMode(2, QHeaderView.ResizeMode.ResizeToContents)
header.setSectionResizeMode(3, QHeaderView.ResizeMode.Stretch)
layout.addWidget(self.prices_table)
# Форма добавления цены
add_group = QGroupBox("Добавить/Обновить цену")
add_layout = QHBoxLayout()
self.region_combo = QComboBox()
regions = self.db.get_all_regions()
for region in regions:
self.region_combo.addItem(region['region_name'], region['region_id'])
self.price_edit = QLineEdit()
self.price_edit.setPlaceholderText("Цена")
add_btn = QPushButton("Добавить/Обновить")
add_btn.clicked.connect(self.add_update_price)
add_layout.addWidget(QLabel("Регион:"))
add_layout.addWidget(self.region_combo)
add_layout.addWidget(QLabel("Цена:"))
add_layout.addWidget(self.price_edit)
add_layout.addWidget(add_btn)
add_group.setLayout(add_layout)
layout.addWidget(add_group)
# Кнопка закрытия
close_btn = QPushButton("Закрыть")
close_btn.clicked.connect(self.close)
layout.addWidget(close_btn)
self.setLayout(layout)
def format_price(self, price: float, region_name: str) -> str:
"""Форматирование цены с правильным символом валюты"""
currency_code, currency_symbol = CURRENCY_MAP.get(region_name, ('USD', '$'))
return f"{currency_symbol}{price:,.2f}"
def load_prices(self):
"""Загрузка цен из БД"""
prices = self.db.get_model_prices(self.model_id)
self.prices_table.setRowCount(len(prices))
for row, price_data in enumerate(prices):
# Регион
region_item = QTableWidgetItem(price_data['region_name'])
region_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.prices_table.setItem(row, 0, region_item)
# Цена с правильной валютой
price_str = self.format_price(price_data['price'], price_data['region_name'])
price_item = QTableWidgetItem(price_str)
price_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.prices_table.setItem(row, 1, price_item)
# Код валюты
currency_code, _ = CURRENCY_MAP.get(price_data['region_name'], ('USD', '$'))
currency_item = QTableWidgetItem(currency_code)
currency_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.prices_table.setItem(row, 2, currency_item)
# Кнопка удаления
delete_btn = QPushButton("Удалить")
delete_btn.clicked.connect(lambda checked, pid=price_data['price_id']: self.delete_price(pid))
self.prices_table.setCellWidget(row, 3, delete_btn)
def add_update_price(self):
"""Добавление или обновление цены"""
try:
region_id = self.region_combo.currentData()
price = float(self.price_edit.text())
self.db.add_or_update_price(self.model_id, region_id, price)
self.load_prices()
self.price_edit.clear()
QMessageBox.information(self, "Успех", "Цена успешно обновлена!")
except ValueError:
QMessageBox.warning(self, "Ошибка", "Введите корректную цену!")
except Exception as e:
QMessageBox.critical(self, "Ошибка", f"Ошибка при обновлении цены: {str(e)}")
def delete_price(self, price_id):
"""Удаление цены"""
reply = QMessageBox.question(
self, "Подтверждение",
"Вы уверены, что хотите удалить эту цену?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
)
if reply == QMessageBox.StandardButton.Yes:
try:
self.db.delete_price(price_id)
self.load_prices()
QMessageBox.information(self, "Успех", "Цена удалена!")
except Exception as e:
QMessageBox.critical(self, "Ошибка", f"Ошибка при удалении: {str(e)}")
class MainWindow(QMainWindow):
"""Главное окно приложения"""
def __init__(self):
super().__init__()
self.db = Database()
self.init_ui()
self.connect_to_db()
def init_ui(self):
self.setWindowTitle("Датасет мобильных устройств")
self.setGeometry(100, 100, 1200, 600)
# Центральный виджет
central_widget = QWidget()
self.setCentralWidget(central_widget)
# Основной layout
layout = QVBoxLayout(central_widget)
# Создаем панель инструментов
self.create_toolbar()
# Создаем вкладки
self.tabs = QTabWidget()
# Вкладка компаний
self.companies_tab = self.create_companies_tab()
self.tabs.addTab(self.companies_tab, "🏢 Компании")
# Вкладка моделей
self.models_tab = self.create_models_tab()
self.tabs.addTab(self.models_tab, "📱 Модели")
# Вкладка аналитики
self.analytics_tab = self.create_analytics_tab()
self.tabs.addTab(self.analytics_tab, "📊 Аналитика")
layout.addWidget(self.tabs)
# Статусная строка
self.status_bar = QStatusBar()
self.setStatusBar(self.status_bar)
self.status_bar.showMessage("Готов к работе")
def create_toolbar(self):
"""Создание панели инструментов"""
toolbar = QToolBar()
self.addToolBar(toolbar)
# Действия
refresh_action = QAction("🔄 Обновить", self)
refresh_action.triggered.connect(self.refresh_data)
toolbar.addAction(refresh_action)
toolbar.addSeparator()
add_company_action = QAction(" Добавить компанию", self)
add_company_action.triggered.connect(self.add_company)
toolbar.addAction(add_company_action)
toolbar.addSeparator()
add_model_action = QAction(" Добавить модель", self)
add_model_action.triggered.connect(self.add_model)
toolbar.addAction(add_model_action)
def create_companies_tab(self) -> QWidget:
"""Создание вкладки компаний"""
widget = QWidget()
layout = QVBoxLayout(widget)
# Панель управления
control_panel = QHBoxLayout()
control_panel.addStretch()
layout.addLayout(control_panel)
# Таблица компаний
self.companies_table = QTableWidget()
self.companies_table.setColumnCount(3)
self.companies_table.setHorizontalHeaderLabels(["ID", "Название компании", "Кол-во моделей"])
self.companies_table.setSortingEnabled(True)
# Настройка размеров столбцов
header = self.companies_table.horizontalHeader()
header.setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents)
header.setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)
header.setSectionResizeMode(2, QHeaderView.ResizeMode.ResizeToContents)
layout.addWidget(self.companies_table)
return widget
def create_models_tab(self) -> QWidget:
"""Создание вкладки моделей"""
widget = QWidget()
layout = QVBoxLayout(widget)
# Панель поиска
search_panel = QHBoxLayout()
self.search_edit = QLineEdit()
self.search_edit.setPlaceholderText("🔍 Поиск по названию, компании, RAM...")
self.search_edit.textChanged.connect(self.search_models)
search_panel.addWidget(self.search_edit)
layout.addLayout(search_panel)
# Таблица моделей
self.models_table = QTableWidget()
self.models_table.setColumnCount(11)
self.models_table.setHorizontalHeaderLabels([
"ID", "Компания", "Модель", "RAM", "Батарея",
"Экран", "Год", "Регионов с ценами", "Действия", "", ""
])
self.models_table.setSortingEnabled(True)
# Настройка размеров столбцов
header = self.models_table.horizontalHeader()
# ID - минимальный размер
header.setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents)
# Компания и Модель - расширяемые
header.setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)
header.setSectionResizeMode(2, QHeaderView.ResizeMode.Stretch)
# Характеристики - по содержимому
for i in range(3, 8):
header.setSectionResizeMode(i, QHeaderView.ResizeMode.ResizeToContents)
# Кнопки действий - фиксированные
for i in range(8, 11):
header.setSectionResizeMode(i, QHeaderView.ResizeMode.Fixed)
header.resizeSection(i, 120)
layout.addWidget(self.models_table)
return widget
def create_analytics_tab(self) -> QWidget:
"""Создание вкладки аналитики"""
widget = QWidget()
layout = QVBoxLayout(widget)
# Статистика по регионам
stats_group = QGroupBox("📊 Статистика цен по регионам")
stats_layout = QVBoxLayout()
self.stats_text = QTextEdit()
self.stats_text.setReadOnly(True)
self.stats_text.setFont(QFont("Consolas", 10))
stats_layout.addWidget(self.stats_text)
refresh_stats_btn = QPushButton("🔄 Обновить статистику")
refresh_stats_btn.clicked.connect(self.update_statistics)
stats_layout.addWidget(refresh_stats_btn)
stats_group.setLayout(stats_layout)
layout.addWidget(stats_group)
return widget
def connect_to_db(self):
"""Подключение к БД"""
if self.db.connect():
self.status_bar.showMessage("✅ Подключено к БД")
self.refresh_data()
else:
QMessageBox.critical(self, "Ошибка", "Не удалось подключиться к БД!")
def refresh_data(self):
"""Обновление всех данных"""
self.load_companies()
self.load_models()
self.update_statistics()
self.status_bar.showMessage("✅ Данные обновлены")
def load_companies(self):
"""Загрузка списка компаний"""
companies = self.db.get_all_companies()
self.companies_table.setRowCount(len(companies))
for row, company in enumerate(companies):
# ID
id_item = QTableWidgetItem(str(company['company_id']))
id_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.companies_table.setItem(row, 0, id_item)
# Название
name_item = QTableWidgetItem(company['company_name'])
name_item.setTextAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
self.companies_table.setItem(row, 1, name_item)
# Количество моделей
count_item = QTableWidgetItem(str(company['models_count']))
count_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.companies_table.setItem(row, 2, count_item)
def load_models(self, search_text=""):
"""Загрузка списка моделей"""
if search_text:
models = self.db.search_models(search_text)
else:
models = self.db.get_all_models()
self.models_table.setRowCount(len(models))
for row, model in enumerate(models):
# ID
id_item = QTableWidgetItem(str(model['model_id']))
id_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.models_table.setItem(row, 0, id_item)
# Компания
company_item = QTableWidgetItem(model['company_name'])
company_item.setTextAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
self.models_table.setItem(row, 1, company_item)
# Модель
model_item = QTableWidgetItem(model['model_name'])
model_item.setTextAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
self.models_table.setItem(row, 2, model_item)
# RAM
ram_item = QTableWidgetItem(model.get('ram', ''))
ram_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.models_table.setItem(row, 3, ram_item)
# Батарея
battery_item = QTableWidgetItem(model.get('battery_capacity', ''))
battery_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.models_table.setItem(row, 4, battery_item)
# Экран
screen_item = QTableWidgetItem(model.get('screen_size', ''))
screen_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.models_table.setItem(row, 5, screen_item)
# Год
year_item = QTableWidgetItem(str(model.get('launched_year', '')))
year_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.models_table.setItem(row, 6, year_item)
# Регионов с ценами
regions_item = QTableWidgetItem(str(model.get('price_regions', 0)))
regions_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.models_table.setItem(row, 7, regions_item)
# Кнопки действий
edit_btn = QPushButton("✏️ Редакт.")
edit_btn.clicked.connect(lambda checked, mid=model['model_id']: self.edit_model(mid))
self.models_table.setCellWidget(row, 8, edit_btn)
price_btn = QPushButton("💰 Цены")
price_btn.clicked.connect(
lambda checked, mid=model['model_id'], name=model['model_name']:
self.manage_prices(mid, name)
)
self.models_table.setCellWidget(row, 9, price_btn)
delete_btn = QPushButton("🗑️ Удалить")
delete_btn.clicked.connect(lambda checked, mid=model['model_id']: self.delete_model(mid))
self.models_table.setCellWidget(row, 10, delete_btn)
def add_company(self):
"""Добавление новой компании"""
name, ok = QInputDialog.getText(self, "Новая компания", "Введите название компании:")
if ok and name:
try:
self.db.add_company(name)
self.refresh_data()
QMessageBox.information(self, "Успех", f"Компания '{name}' добавлена!")
except Exception as e:
QMessageBox.critical(self, "Ошибка", f"Ошибка при добавлении: {str(e)}")
def add_model(self):
"""Добавление новой модели"""
dialog = ModelDialog(self)
if dialog.exec() == QDialog.DialogCode.Accepted:
try:
model_data = dialog.get_data()
self.db.add_model(model_data)
self.refresh_data()
QMessageBox.information(self, "Успех", "Модель добавлена!")
except Exception as e:
QMessageBox.critical(self, "Ошибка", f"Ошибка при добавлении: {str(e)}")
def edit_model(self, model_id):
"""Редактирование модели"""
model_data = self.db.get_model_by_id(model_id)
dialog = ModelDialog(self, model_data)
if dialog.exec() == QDialog.DialogCode.Accepted:
try:
updated_data = dialog.get_data()
self.db.update_model(model_id, updated_data)
self.refresh_data()
QMessageBox.information(self, "Успех", "Модель обновлена!")
except Exception as e:
QMessageBox.critical(self, "Ошибка", f"Ошибка при обновлении: {str(e)}")
def delete_model(self, model_id):
"""Удаление модели"""
reply = QMessageBox.question(
self, "Подтверждение",
"Вы уверены, что хотите удалить эту модель?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
)
if reply == QMessageBox.StandardButton.Yes:
try:
self.db.delete_model(model_id)
self.refresh_data()
QMessageBox.information(self, "Успех", "Модель удалена!")
except Exception as e:
QMessageBox.critical(self, "Ошибка", f"Ошибка при удалении: {str(e)}")
def manage_prices(self, model_id, model_name):
"""Управление ценами модели"""
dialog = PriceDialog(self, model_id, model_name)
dialog.exec()
self.refresh_data()
def search_models(self, text):
"""Поиск моделей"""
self.load_models(text)
def update_statistics(self):
"""Обновление статистики"""
try:
stats = self.db.get_price_statistics()
stats_text = "📊 СТАТИСТИКА ЦЕН ПО РЕГИОНАМ\n" + "="*60 + "\n\n"
for stat in stats:
region_name = stat['region_name']
currency_code, currency_symbol = CURRENCY_MAP.get(region_name, ('USD', '$'))
stats_text += f"🌍 {region_name} ({currency_code}):\n"
stats_text += f" • Моделей с ценами: {stat['models_count']}\n"
stats_text += f" • Средняя цена: {currency_symbol}{stat['avg_price']:,.2f}\n"
stats_text += f" • Минимальная цена: {currency_symbol}{stat['min_price']:,.2f}\n"
stats_text += f" • Максимальная цена: {currency_symbol}{stat['max_price']:,.2f}\n\n"
self.stats_text.setText(stats_text)
except Exception as e:
self.stats_text.setText(f"Ошибка при загрузке статистики: {str(e)}")
def closeEvent(self, event):
"""Обработка закрытия окна"""
self.db.disconnect()
event.accept()