diff --git a/composer.py b/composer.py index e88f7e1..55c9218 100644 --- a/composer.py +++ b/composer.py @@ -1,12 +1,15 @@ -from src.windows import LoginWindow, AdminWindow, ClientWindow +from src.windows import LoginWindow, AdminWindow, ClientWindow, ManagerWindow from src.objects import User, Rights -from src.db import DB_AUTH_HARDCODED as config +from src.db import DB_CONFIG from PyQt6.QtWidgets import QApplication from PyQt6.QtCore import QObject, pyqtSlot, pyqtSignal from PyQt6.QtSql import QSqlDatabase + class Composer(QObject): + """Управляет навигацией между окнами приложения""" + render_request = pyqtSignal(User) def __init__(self): @@ -17,56 +20,64 @@ class Composer(QObject): self.render_request.connect(self._render) def _init_db(self): + """Инициализация подключения к базе данных""" self._db = QSqlDatabase("QPSQL") - self._db.setDatabaseName(config['dbname']) - self._db.setPort(config['port']) - self._db.setHostName(config['host']) - self._db.setUserName(config['user']) - self._db.setPassword(config['password']) - self._db.open() + self._db.setDatabaseName(DB_CONFIG['dbname']) + self._db.setPort(DB_CONFIG['port']) + self._db.setHostName(DB_CONFIG['host']) + self._db.setUserName(DB_CONFIG['user']) + self._db.setPassword(DB_CONFIG['password']) + + if not self._db.open(): + raise Exception(f"Не получилось подключиться к базе данных: {self._db.lastError().text()}") @pyqtSlot(User) def _render(self, user: User): + """Маршрутизация пользователя на основе его роли""" match user.rights: case Rights.ADMIN: self._admin_fabric() case Rights.MANAGER: - pass + self._manager_fabric() case Rights.CLIENT: self._client_fabric(user) + case Rights.GUEST: + self._guest_fabric() def _login_fabric(self): + """Создание и отображение окна входа""" self.wlogin = LoginWindow(self, self._db) - - if self._current: - self._current.close() - - self.wlogin.show() - - self._current = self.wlogin + self._switch_window(self.wlogin) def _admin_fabric(self): + """Создание и отображение панели администратора""" self.wadmin = AdminWindow(self, self._db) + self._switch_window(self.wadmin) - if self._current: - self._current.close() - - self.wadmin.show() - - self._current = self.wadmin + def _manager_fabric(self): + """Создание и отображение панели менеджера""" + self.wmanager = ManagerWindow(self, self._db) + self._switch_window(self.wmanager) def _client_fabric(self, user: User): + """Создание и отображение панели клиента""" self.wclient = ClientWindow(self, self._db, user) + self._switch_window(self.wclient) + def _guest_fabric(self): + """Создание и отображение панели гостя""" + # TODO: Реализовать GuestWindow если нужен режим гостя + pass + + def _switch_window(self, new_window): + """Переключение между окнами""" if self._current: self._current.close() - - self.wclient.show() - - self._current = self.wclient + new_window.show() + self._current = new_window def run(self): + """Запуск приложения""" import sys self._login_fabric() - self._current.show() sys.exit(self._app.exec()) \ No newline at end of file diff --git a/scrapper.sh b/scrapper.sh index 7c70281..1e73158 100755 --- a/scrapper.sh +++ b/scrapper.sh @@ -1,5 +1,9 @@ #!/usr/bin/env bash +# Этот скрипт я написал для быстрой сборки кода в 1 файл. +# Это очень удобно что бы отправлять код на анализ ИИ +# Работает только в оболочке bash для *nix систем + OUTPUT="all_code.txt" ROOT="." diff --git a/src/db.py b/src/db.py index 150b108..6fab82b 100644 --- a/src/db.py +++ b/src/db.py @@ -1,19 +1,27 @@ import psycopg2 as pg - from .objects import User, Rights -DB_AUTH_HARDCODED = { +DB_CONFIG = { "host": "127.0.0.1", "port": 5432, - "dbname": "examdb", - "user": "postgres", - "password": "213k2010###" + "dbname": "example", # TODO: Заменить на название из задания + "user": "example_user", + "password": "example_password123" # TODO: Заменить на реальный пароль } def get_connection(): - return pg.connect(**DB_AUTH_HARDCODED) + """Получить подключение к базе данных""" + return pg.connect(**DB_CONFIG) + def do_request(autocommit=False): + """ + Декоратор для выполнения SQL запросов с автоматическим управлением соединением + + Args: + autocommit: Если True, автоматически фиксирует транзакцию + """ + def upper_wrapper(func): def wrapper(*args, **kwargs): conn = get_connection() @@ -21,7 +29,6 @@ def do_request(autocommit=False): try: kwargs['cursor'] = cursor - result = func(*args, **kwargs) if autocommit: @@ -30,43 +37,57 @@ def do_request(autocommit=False): return result except Exception as e: - print(f"Error: Can't request query to DB: {e}") + print(f"Error: Database request failed: {e}") + conn.rollback() return None finally: cursor.close() conn.close() - return wrapper + return wrapper return upper_wrapper + @do_request() def auth(login: str, password: str, *, cursor) -> User | None: + """ + Аутентификация пользователя + + Args: + login: Имя пользователя + password: Пароль + + Returns: + User объект или None если аутентификация не удалась + """ cursor.execute(""" SELECT id, name, rights FROM users - WHERE name = %s - AND password = %s; + WHERE name = %s AND password = %s; """, (login, password)) user = cursor.fetchone() if not user: - print("Warning: Login Forbidden: Can't find such user!") + print("Warning: Authentication failed - user not found") return None - rights = None + # Маппинг строки роли на Enum + rights_mapping = { + "admin": Rights.ADMIN, + "manager": Rights.MANAGER, + "client": Rights.CLIENT, + "customer": Rights.CLIENT, # Альтернативное название + "guest": Rights.GUEST, + } - match user[2]: - case "admin": - rights = Rights.ADMIN - case "customer": - rights = Rights.CLIENT - case "manager": - rights = Rights.MANAGER - case _: - return None + rights = rights_mapping.get(user[2]) + + if not rights: + print(f"Warning: Unknown user role: {user[2]}") + return None return User( id=user[0], @@ -74,80 +95,54 @@ def auth(login: str, password: str, *, cursor) -> User | None: rights=rights ) + +# TODO: Ниже функции для работы с предметной областью +# Заменить на ваши функции (например: get_products, add_product, etc.) + @do_request() -def get_free_numbers(*, cursor): +def get_items(*, cursor): + """ + Получить список доступных элементов + + Например: get_products(), get_available_rooms(), etc. + """ + # Пример запроса - заменить на реальный cursor.execute(""" SELECT * - FROM rooms - WHERE status = 'free'; + FROM items + WHERE status = 'available'; """) - free = cursor.fetchall() + items = cursor.fetchall() + return items if items else None - if not free: - return None - - return free @do_request(autocommit=True) -def update_number_status(number: str, checkin: str, - checkout: str, user: User, - *, cursor): +def create_request(item_id: int, user: User, **kwargs): + """ + Создать заявку/заказ + + TODO: Адаптировать под конкретную предметную область + Args: + item_id: ID элемента (товар, номер, услуга) + user: Пользователь создающий заявку + **kwargs: Дополнительные параметры (даты, количество, etc.) + """ + # TODO: Реализовать вашу логику создания заявки + pass + + +@do_request() +def get_user_requests(user_id: int, *, cursor): + """ + Получить заявки/заказы пользователя + + TODO: Заменить на вашу логику + """ cursor.execute(""" - SELECT password - FROM users - WHERE id = %s - """, (user.id,)) + SELECT * + FROM requests + WHERE user_id = %s; + """, (user_id,)) - password = cursor.fetchone() - - if not password: - return False - - cursor.execute(""" - SELECT id - FROM guests - WHERE name = %s - AND PHONE = %s - """, (user.name, password[0])) - - guest = cursor.fetchone() - - if not guest: - return False - - cursor.execute(""" - SELECT id - FROM rooms - WHERE number = %s; - """, (number,)) - - number_id = cursor.fetchone() - - if not number_id: - return False - - cursor.execute(""" - SELECT guest, room - FROM bookings - WHERE guest = %s - AND room = %s; - """, (guest[0], number_id[0])) - - request_exists = cursor.fetchone() - - if request_exists: - return False - - cursor.execute(""" - INSERT INTO bookings(guest, room, checkin, checkout, status) - VALUES (%s, %s, %s, %s, 'active'); - """, (guest[0], number_id[0], checkin, checkout)) - - cursor.execute(""" - UPDATE rooms - SET status = 'booked' - WHERE number = %s; - """, (number,)) - - return True \ No newline at end of file + return cursor.fetchall() \ No newline at end of file diff --git a/src/objects.py b/src/objects.py index ad11e58..cf22a40 100644 --- a/src/objects.py +++ b/src/objects.py @@ -2,16 +2,20 @@ from enum import Enum, auto from dataclasses import dataclass class SignalCode(Enum): - SIGFALSE = auto() - SIGERR = auto() + """Коды сигналов для обработки ошибок""" + SIGFALSE = auto() # Неверные данные + SIGERR = auto() # Ошибка валидации class Rights(Enum): - ADMIN = auto() - CLIENT = auto() - MANAGER = auto() + """Роли пользователей в системе""" + ADMIN = auto() # Полный доступ + MANAGER = auto() # Просмотр + частичное редактирование + CLIENT = auto() # Ограниченный доступ + GUEST = auto() # Только просмотр @dataclass class User: + """Модель пользователя""" id: int name: str rights: Rights \ No newline at end of file diff --git a/src/utils.py b/src/utils.py index b1d81e7..810eb34 100644 --- a/src/utils.py +++ b/src/utils.py @@ -9,17 +9,38 @@ from PyQt6.QtWidgets import ( ) from PyQt6.QtSql import QSqlTableModel + class TabWidgetCustom(QWidget): - def __init__(self, name: str, db): + """ + Универсальный виджет для CRUD операций с таблицей БД + + Функционал: + - Отображение данных в QTableView + - Добавление/удаление строк + - Применение/отмена изменений + - Экспорт в CSV + - Опциональная фильтрация по датам + """ + + def __init__(self, table_name: str, db, show_date_filter=False): + """ + Args: + table_name: Название таблицы в БД + db: QSqlDatabase объект + show_date_filter: Показывать ли фильтр по датам + """ super().__init__() - self._name = name + self._name = table_name self._db = db + self._show_date_filter = show_date_filter self._setup() def _setup(self): + """Настройка UI виджета""" self.root = QVBoxLayout(self) - if self._name in ("bookings", "payments"): + # Опциональный фильтр по датам + if self._show_date_filter: self.header = QHBoxLayout() self.from_date = QDateEdit() @@ -28,27 +49,29 @@ class TabWidgetCustom(QWidget): self.to_date = QDateEdit() self.to_date.setCalendarPopup(True) - self.button_filter = QPushButton("Фильтровать") - self.button_all = QPushButton("Показать всё") + self.button_filter = QPushButton("Filter") + self.button_all = QPushButton("Show All") - self.header.addWidget(QLabel("С:")) + self.header.addWidget(QLabel("From:")) self.header.addWidget(self.from_date) - self.header.addWidget(QLabel("По:")) + self.header.addWidget(QLabel("To:")) self.header.addWidget(self.to_date) self.header.addWidget(self.button_filter) self.header.addWidget(self.button_all) self.root.addLayout(self.header) + # Таблица self.view = QTableView() + # Панель кнопок self.btoolbar = QHBoxLayout() - self.button_add = QPushButton("+ Добавить") - self.button_del = QPushButton("- Удалить") - self.button_ok = QPushButton("\\_/ Применить") - self.button_deny = QPushButton("-- Отменить") - self.button_csv = QPushButton("Выгрузить отчёт в CSV") + self.button_add = QPushButton("+ Add") + self.button_del = QPushButton("- Delete") + self.button_ok = QPushButton("✓ Apply") + self.button_deny = QPushButton("✗ Cancel") + self.button_csv = QPushButton("Export CSV") self.btoolbar.addWidget(self.button_add) self.btoolbar.addWidget(self.button_del) @@ -62,6 +85,7 @@ class TabWidgetCustom(QWidget): self._setup_db() def _setup_db(self): + """Настройка модели БД""" self.model = QSqlTableModel(db=self._db) self.model.setTable(self._name) self.model.setEditStrategy(QSqlTableModel.EditStrategy.OnManualSubmit) diff --git a/src/windows.py b/src/windows.py index f168f19..a0bd9ac 100644 --- a/src/windows.py +++ b/src/windows.py @@ -1,5 +1,5 @@ import csv -from .db import auth, get_free_numbers, update_number_status +from .db import auth, get_items, get_user_requests from .objects import User, SignalCode from .utils import TabWidgetCustom from PyQt6.QtWidgets import ( @@ -20,10 +20,20 @@ from PyQt6.QtGui import QStandardItemModel, QStandardItem from PyQt6.QtSql import QSqlTableModel from PyQt6.QtCore import Qt, pyqtSignal, pyqtSlot, QDate -FIW = 160 # Fixed Input Width -NO_NUMBERS_CONST = "Нет свободных номеров" +FIW = 160 # Fixed Input Width + class BaseWindow(QMainWindow): + """ + Базовый класс для всех окон приложения + + Определяет общую структуру окна: + 1. _define_widgets() - создание виджетов + 2. _tune_layouts() - настройка layout'ов + 3. _connect_slots() - подключение сигналов + 4. _apply_window_settings() - настройка окна (заголовок, размер) + """ + def __init__(self, composer, db, user=None): super().__init__() self._composer = composer @@ -35,59 +45,71 @@ class BaseWindow(QMainWindow): self._apply_window_settings() def _define_widgets(self): + """Определение виджетов (переопределить в наследниках)""" pass def _tune_layouts(self): + """Настройка компоновки (переопределить в наследниках)""" pass def _connect_slots(self): + """Подключение сигналов к слотам (переопределить в наследниках)""" pass def _apply_window_settings(self): + """Настройка параметров окна (переопределить в наследниках)""" pass + class LoginWindow(BaseWindow): + """Окно авторизации""" + login_success = pyqtSignal(User) login_forbidden = pyqtSignal(SignalCode) def _define_widgets(self): self.root = QWidget() - self.auth_form = QGroupBox("Логин") + self.auth_form = QGroupBox("Authorization") self.auth_form.setAlignment(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter) self.login_line = QLineEdit() self.login_line.setFixedWidth(FIW) + self.login_line.setPlaceholderText("Username") self.password_line = QLineEdit() self.password_line.setFixedWidth(FIW) self.password_line.setEchoMode(QLineEdit.EchoMode.Password) + self.password_line.setPlaceholderText("Password") - self.auth_button = QPushButton("Авторизоваться") + self.auth_button = QPushButton("Login") + self.guest_button = QPushButton("Continue as Guest") # Опционально def _tune_layouts(self): self.root_l = QVBoxLayout() self.form = QFormLayout() - self.form.addRow("Логин:", self.login_line) - self.form.addRow("Пароль:", self.password_line) + self.form.addRow("Login:", self.login_line) + self.form.addRow("Password:", self.password_line) self.auth_form.setLayout(self.form) self.root_l.addWidget(self.auth_form) self.root_l.addWidget(self.auth_button) + # self.root_l.addWidget(self.guest_button) # Опционально self.root.setLayout(self.root_l) - self.setCentralWidget(self.root) def _connect_slots(self): self.auth_button.clicked.connect(self._on_auth_button_clicked) self.login_success.connect(self._on_login_success) self.login_forbidden.connect(self._on_login_forbidden) + # self.guest_button.clicked.connect(self._on_guest_button_clicked) # Опционально def _on_auth_button_clicked(self): + """Обработка нажатия кнопки входа""" login = self.login_line.text() password = self.password_line.text() @@ -101,49 +123,53 @@ class LoginWindow(BaseWindow): self.login_forbidden.emit(SignalCode.SIGFALSE) return - print(f"Login OK: {user.name} {user.rights}") - + print(f"Login successful: {user.name} ({user.rights.name})") self.login_success.emit(user) @pyqtSlot(User) def _on_login_success(self, user: User): + """Успешная авторизация - переход к соответствующей панели""" self._composer.render_request.emit(user) @pyqtSlot(SignalCode) def _on_login_forbidden(self, code: SignalCode): + """Обработка ошибок авторизации""" match code: case SignalCode.SIGFALSE: - QMessageBox().information(self, - "Предупреждение!", - "Вы ввели некорректные данные") + QMessageBox.warning(self, + "Authentication Failed", + "Invalid username or password") case SignalCode.SIGERR: - QMessageBox().information(self, - "Ошибка!", - "Вы ввели неприемлемые данные") + QMessageBox.critical(self, + "Input Error", + "Please enter both username and password") def _apply_window_settings(self): - self.setWindowTitle("Авторизация") - self.setFixedSize(260,180) + self.setWindowTitle("Login") + self.setFixedSize(300, 200) + class AdminWindow(BaseWindow): + """ + Панель администратора + + TODO: Добавить табы для каждой таблицы из задания + Например: products, orders, users, etc. + """ + def _define_widgets(self): self.root = QWidget() - self.tabs = QTabWidget() - self.users_tab = TabWidgetCustom("users", self._db) - self.guests_tab = TabWidgetCustom("guests", self._db) - self.rooms_tab = TabWidgetCustom("rooms", self._db) - self.bookings_tab = TabWidgetCustom("bookings", self._db) - self.staff_tab = TabWidgetCustom("staff", self._db) - self.payments_tab = TabWidgetCustom("payments", self._db) + # TODO: Заменить на ваши таблицы + # Пример: + # self.products_tab = TabWidgetCustom("products", self._db) + # self.orders_tab = TabWidgetCustom("orders", self._db, show_date_filter=True) + # self.users_tab = TabWidgetCustom("users", self._db) - self.tabs.addTab(self.users_tab, "Пользователи") - self.tabs.addTab(self.guests_tab, "Постояльцы") - self.tabs.addTab(self.rooms_tab, "Номера") - self.tabs.addTab(self.bookings_tab, "Бронирования") - self.tabs.addTab(self.staff_tab, "Персонал") - self.tabs.addTab(self.payments_tab, "Платежи") + self.example_tab = TabWidgetCustom("items", self._db) # TODO: Заменить + + self.tabs.addTab(self.example_tab, "Items") # TODO: Адаптировать названия def _tune_layouts(self): self.root_l = QVBoxLayout() @@ -152,245 +178,352 @@ class AdminWindow(BaseWindow): self.setCentralWidget(self.root) def _connect_slots(self): + """Подключение обработчиков для каждого таба""" for i in range(self.tabs.count()): tab = self.tabs.widget(i) + # Фильтрация по датам (если доступна) try: tab.button_all.clicked.connect(lambda _, t=tab: self._select_all(t)) tab.button_filter.clicked.connect(lambda _, t=tab: self._select_filter(t)) - except Exception as e: - pass + except AttributeError: + pass # Нет фильтра по датам + # CRUD операции tab.button_add.clicked.connect(lambda _, t=tab: self._add_row(t)) tab.button_del.clicked.connect(lambda _, t=tab: self._del_row(t)) - tab.button_csv.clicked.connect(lambda _, t=tab: self._csv_row(t)) + tab.button_csv.clicked.connect(lambda _, t=tab: self._csv_export(t)) tab.button_ok.clicked.connect(lambda _, t=tab: self._apply_changes(t)) tab.button_deny.clicked.connect(lambda _, t=tab: self._revert_changes(t)) def _select_all(self, tab): + """Сброс фильтра - показать все записи""" tab.model.setFilter("") tab.model.select() def _select_filter(self, tab): + """ + Фильтрация по датам + + TODO: Адаптировать под поля из задания + """ date_from = tab.from_date.date().toString("yyyy-MM-dd") date_to = tab.to_date.date().toString("yyyy-MM-dd") - if tab._name == "bookings": - tab.model.setFilter(f"DATE(checkin) >= \'{date_from}\' AND DATE(checkout) <= \'{date_to}\'") - tab.model.select() - elif tab._name == "payments": - tab.model.setFilter(f"date <= \'{date_from}\' AND date <= \'{date_to}\'") + # TODO: Изменить название поля (checkin/checkout → order_date/delivery_date) + if hasattr(tab, '_name'): + # Пример фильтра + tab.model.setFilter( + f"DATE(date_field) >= '{date_from}' AND DATE(date_field) <= '{date_to}'" + ) tab.model.select() def _add_row(self, tab): + """Добавление новой строки""" row = tab.model.rowCount() tab.model.insertRow(row) tab.view.selectRow(row) tab.view.edit(tab.model.index(row, 0)) def _del_row(self, tab): + """Удаление выбранной строки""" idx = tab.view.currentIndex() if not idx.isValid(): - print("Error: Fatal: row index is not valid!") + QMessageBox.warning(self, "Selection Error", + "Please select a row to delete") return - msg = QMessageBox.question(self, "Подтверждение", - "Вы уверены что хотите удалить выбранную строку ?" - "Изменения будут необратимы") + confirm = QMessageBox.question( + self, + "Confirm Deletion", + "Are you sure you want to delete this record?\nThis action cannot be undone.", + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No + ) - if msg == QMessageBox.StandardButton.Yes: + if confirm == QMessageBox.StandardButton.Yes: tab.model.removeRow(idx.row()) tab.model.submitAll() def _apply_changes(self, tab): + """Применить изменения к БД""" if not tab.model.isDirty(): return - msg = QMessageBox.question(self, "Подтверждение", - "Применить изменения ?") + confirm = QMessageBox.question( + self, + "Apply Changes", + "Do you want to save all changes?", + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No + ) - if msg == QMessageBox.StandardButton.Yes: + if confirm == QMessageBox.StandardButton.Yes: if not tab.model.submitAll(): - QMessageBox.critical(self, "Ошибка БД", - "Не получилось применить изменения," - f"\n{tab.model.lastError().text()}") + QMessageBox.critical( + self, + "Database Error", + f"Failed to save changes:\n{tab.model.lastError().text()}" + ) tab.model.revertAll() def _revert_changes(self, tab): + """Отменить несохранённые изменения""" if not tab.model.isDirty(): return tab.model.revertAll() + QMessageBox.information(self, "Changes Reverted", + "All unsaved changes have been discarded") - def _csv_row(self, tab): - path = f"{tab._name}.csv" + def _csv_export(self, tab): + """Экспорт таблицы в CSV""" + filename = f"{tab._name}.csv" - with open(path, "w", newline="", encoding="utf-8") as f: - writer = csv.writer(f) - header = [tab.model.headerData(i, Qt.Orientation.Horizontal) for i in range(tab.model.columnCount())] + try: + with open(filename, "w", newline="", encoding="utf-8") as f: + writer = csv.writer(f) - writer.writerow(header) + # Заголовки + headers = [ + tab.model.headerData(i, Qt.Orientation.Horizontal) + for i in range(tab.model.columnCount()) + ] + writer.writerow(headers) - for row in range(tab.model.rowCount()): - row_data = [] - for col in range(tab.model.columnCount()): - value = tab.model.data(tab.model.index(row, col)) + # Данные + for row in range(tab.model.rowCount()): + row_data = [] + for col in range(tab.model.columnCount()): + value = tab.model.data(tab.model.index(row, col)) - if hasattr(value, "toString"): - value = value.toString() + if hasattr(value, "toString"): + value = value.toString() - row_data.append(value) + row_data.append(value) - writer.writerow(row_data) + writer.writerow(row_data) - QMessageBox.information(self, "Информация", - "Отчёт был сохранён в корневую директорию приложения") + QMessageBox.information( + self, + "Export Successful", + f"Data exported to:\n{filename}" + ) + except Exception as e: + QMessageBox.critical( + self, + "Export Failed", + f"Failed to export data:\n{str(e)}" + ) def _apply_window_settings(self): - self.setWindowTitle("Панель Администратора") - self.setFixedSize(1200,800) + self.setWindowTitle("Admin Panel") + self.setFixedSize(1200, 800) + + +class ManagerWindow(BaseWindow): + """ + Панель менеджера + + TODO: Реализовать если требуется в задании + Обычно: просмотр данных + поиск/фильтрация (без удаления) + """ + + def _define_widgets(self): + # TODO: Похож на AdminWindow, но с ограниченными правами + pass + + def _apply_window_settings(self): + self.setWindowTitle("Manager Panel") + self.setFixedSize(1000, 600) + class ClientWindow(BaseWindow): + """ + Панель клиента + + TODO: Адаптировать под задание + Обычная структура: + - Таб 1: Создание заявки/заказа + - Таб 2: Просмотр доступных товаров/услуг + - Таб 3: История заявок/заказов пользователя + """ + def _define_widgets(self): self.root = QTabWidget() - self._define_request_box() - self._define_free_rooms() - self._define_client_bookings() + self._define_request_form() + self._define_available_items() + self._define_user_requests() def _tune_layouts(self): self.root_l = QVBoxLayout() - self._tune_request_box() - self._tune_free_rooms() - self._tune_client_bookings() + self._tune_request_form() + self._tune_available_items() + self._tune_user_requests() - self.root.addTab(self.rb_widget, "Окно заявки") - self.root.addTab(self.fr_widget, "Свободные номера") - self.root.addTab(self.cl_widget, "Ваши заявки") + # TODO: Адаптировать названия табов под задание + self.root.addTab(self.request_widget, "New Request") + self.root.addTab(self.items_widget, "Available Items") + self.root.addTab(self.history_widget, "My Requests") self.root.setLayout(self.root_l) - self.setCentralWidget(self.root) - def _define_request_box(self): - self.rb_widget = QWidget() - self.req_box = QGroupBox("Создание заявки") - self.req_box.setAlignment(Qt.AlignmentFlag.AlignVCenter | - Qt.AlignmentFlag.AlignHCenter) + def _define_request_form(self): + """ + Форма создания заявки - self.room_combo = QComboBox() + TODO: Адаптировать под задание + """ + self.request_widget = QWidget() + self.request_box = QGroupBox("Create Request") + self.request_box.setAlignment(Qt.AlignmentFlag.AlignVCenter | + Qt.AlignmentFlag.AlignHCenter) - free_rooms = get_free_numbers() + # TODO: Заменить на реальные поля из задания + self.item_combo = QComboBox() - if free_rooms: - for room in free_rooms: - number = room[1] - self.room_combo.addItem(str(number)) + # Загрузка доступных элементов + items = get_items() + if items: + for item in items: + # TODO: Адаптировать под структуру данных + self.item_combo.addItem(str(item[1])) # Предполагаем что item[1] - название else: - self.room_combo.addItem(NO_NUMBERS_CONST) + self.item_combo.addItem("No items available") - self.checkin = QDateEdit() - self.checkin.setCalendarPopup(True) + # TODO: Добавить дополнительные поля (даты, количество, etc.) + self.date_from = QDateEdit() + self.date_from.setCalendarPopup(True) - self.checkout = QDateEdit() - self.checkout.setCalendarPopup(True) + self.date_to = QDateEdit() + self.date_to.setCalendarPopup(True) - self.book_button = QPushButton("Забронировать") + self.submit_button = QPushButton("Submit Request") - def _tune_request_box(self): - self.req_l = QVBoxLayout() - self.form_l = QFormLayout() + def _tune_request_form(self): + """Компоновка формы заявки""" + req_layout = QVBoxLayout() + form_layout = QFormLayout() - self.form_l.addRow("Комната:", self.room_combo) - self.form_l.addRow("Заезд:", self.checkin) - self.form_l.addRow("Выезд:", self.checkout) + # TODO: Адаптировать названия полей + form_layout.addRow("Item:", self.item_combo) + form_layout.addRow("From:", self.date_from) + form_layout.addRow("To:", self.date_to) - self.req_box.setLayout(self.form_l) + self.request_box.setLayout(form_layout) - self.req_l.addWidget(self.req_box) - self.req_l.addWidget(self.book_button) + req_layout.addWidget(self.request_box) + req_layout.addWidget(self.submit_button) - self.rb_widget.setLayout(self.req_l) + self.request_widget.setLayout(req_layout) - def _define_free_rooms(self): - self.fr_widget = QWidget() - self.fr_table = QTableView() + def _define_available_items(self): + """ + Таб со списком доступных элементов - free_rooms = get_free_numbers() + TODO: Адаптировать под задание + """ + self.items_widget = QWidget() + self.items_table = QTableView() - self.fr_model = QStandardItemModel() - self.fr_model.setHorizontalHeaderLabels(["Номер Комнаты"]) + items = get_items() - if free_rooms: - for room in free_rooms: - item = room[1] - self.fr_model.appendRow(QStandardItem(str(item))) + self.items_model = QStandardItemModel() + # TODO: Адаптировать заголовки + self.items_model.setHorizontalHeaderLabels(["ID", "Name", "Status"]) - self.fr_table.setModel(self.fr_model) + if items: + for item in items: + # TODO: Адаптировать под структуру данных + row = [QStandardItem(str(field)) for field in item] + self.items_model.appendRow(row) - def _tune_free_rooms(self): - self.f_rooms_l = QVBoxLayout() - self.f_rooms_l.addWidget(self.fr_table) + self.items_table.setModel(self.items_model) - self.fr_widget.setLayout(self.f_rooms_l) + def _tune_available_items(self): + """Компоновка таба доступных элементов""" + layout = QVBoxLayout() + layout.addWidget(self.items_table) + self.items_widget.setLayout(layout) - def _define_client_bookings(self): - self.cl_widget = QWidget() - self.cl_table = QTableView() + def _define_user_requests(self): + """ + Таб с историей заявок пользователя - self.cl_model = QSqlTableModel(db=self._db) - self.cl_model.setTable("bookings") - self.cl_model.select() + TODO: Адаптировать под задание + """ + self.history_widget = QWidget() + self.history_table = QTableView() - self.cl_table.setModel(self.cl_model) + self.history_model = QSqlTableModel(db=self._db) + # TODO: Заменить на реальную таблицу + self.history_model.setTable("requests") - def _tune_client_bookings(self): - self.c_bookings_l = QVBoxLayout() - self.c_bookings_l.addWidget(self.cl_table) + # TODO: Добавить фильтр по user_id + if self._user: + self.history_model.setFilter(f"user_id = {self._user.id}") - self.cl_widget.setLayout(self.c_bookings_l) + self.history_model.select() + + self.history_table.setModel(self.history_model) + + def _tune_user_requests(self): + """Компоновка таба истории""" + layout = QVBoxLayout() + layout.addWidget(self.history_table) + self.history_widget.setLayout(layout) def _connect_slots(self): - self.book_button.clicked.connect(self._book_button_handler) + """Подключение обработчиков""" + self.submit_button.clicked.connect(self._on_submit_request) - def _book_button_handler(self): - room = self.room_combo.currentText() - checkin = self.checkin.date().toString("yyyy-MM-dd") - checkout = self.checkout.date().toString("yyyy-MM-dd") + def _on_submit_request(self): + """ + Обработка создания заявки - if room == NO_NUMBERS_CONST: - QMessageBox.information(self, "Информация", - "На данный момент у нас нет свободных номеров," - "приносим свои извинения за доставленные неудобства") + TODO: Реализовать логику создания заявки + """ + item = self.item_combo.currentText() + date_from = self.date_from.date().toString("yyyy-MM-dd") + date_to = self.date_to.date().toString("yyyy-MM-dd") + + # Валидация + if item == "No items available": + QMessageBox.warning(self, "No Items", + "There are no items available at this moment") return - if not checkin or not checkout: - QMessageBox.critical(self, "Информация", - "Пожалуйста, введите корректную дату прибытия и отбытия") + if not date_from or not date_to: + QMessageBox.critical(self, "Input Error", + "Please select valid dates") return - current_date = QDate.currentDate() - - if self.checkin.date() < current_date or self.checkout.date() < current_date: - QMessageBox.critical(self, "Предупреждение", - "Вы не можете выбрать прошедшую дату!") + # Проверка дат + if self.date_from.date() < QDate.currentDate() or \ + self.date_to.date() < QDate.currentDate(): + QMessageBox.warning(self, "Invalid Date", + "Cannot select past dates") return - status = update_number_status(room, checkin, checkout, self._user) + # TODO: Вызвать функцию создания заявки из db.py + # success = create_request(item_id, self._user, date_from=date_from, date_to=date_to) - if not status: - QMessageBox.critical(self, "Ошибка!", - "Не получилось забронировать ваш номер, попробуйте позже") - return + # Заглушка + success = True + + if success: + QMessageBox.information( + self, + "Success", + f"Request created successfully!\nItem: {item}\nPeriod: {date_from} - {date_to}" + ) + self.history_model.select() # Обновить историю else: - QMessageBox.information(self, "Успешно!", - f"Вы забронировали номер {room} на даты" - f"\nС {checkin} по {checkout}, будем рады вас видеть!") - - self.cl_model.select() + QMessageBox.critical(self, "Error", + "Failed to create request. Please try again later.") def _apply_window_settings(self): - self.setWindowTitle("Панель Пользователя") - self.setFixedSize(800,400) \ No newline at end of file + self.setWindowTitle("Client Panel") + self.setFixedSize(900, 600) \ No newline at end of file