From c1b34ce7d77678620da65bc9d1a70b913ef84282 Mon Sep 17 00:00:00 2001 From: slava Date: Tue, 22 Apr 2025 12:22:56 +0700 Subject: [PATCH] Implement AccountWindow and TransactionWindow for account management Added AccountWindow to display account tables linked to devices and TransactionWindow for viewing account details. Enhanced DeviceDAO and AccountDAO to support additional filtering and data retrieval functionalities. --- database/dao/AccountDAO.cpp | 32 ++++++++++ database/dao/AccountDAO.h | 2 + database/dao/DeviceDAO.cpp | 13 +++- database/dao/DeviceDAO.h | 2 +- views/AccountWindow.cpp | 100 +++++++++++++++++++++++++++++ views/AccountWindow.h | 14 ++++ views/MainWindow.cpp | 124 ++++++++++++++++++++++++------------ views/MainWindow.h | 10 ++- views/TransactionWindow.cpp | 21 ++++++ views/TransactionWindow.h | 10 +++ 10 files changed, 281 insertions(+), 47 deletions(-) create mode 100644 views/AccountWindow.cpp create mode 100644 views/AccountWindow.h create mode 100644 views/TransactionWindow.cpp create mode 100644 views/TransactionWindow.h diff --git a/database/dao/AccountDAO.cpp b/database/dao/AccountDAO.cpp index 4b1dee1..9c9a9b8 100644 --- a/database/dao/AccountDAO.cpp +++ b/database/dao/AccountDAO.cpp @@ -74,3 +74,35 @@ QList AccountDAO::getAccounts(const QString &deviceId, const QStrin return accounts; } + +AccountInfo AccountDAO::getAccountById(const int accountId) { + AccountInfo acc; + QSqlQuery query(DatabaseManager::instance().database()); + + query.prepare(R"( + SELECT id, device_id, app_name, card_name, amount, update_time, status, description + FROM accounts + WHERE id = :id + LIMIT 1 + )"); + + query.bindValue(":id", accountId); + + if (!query.exec()) { + qWarning() << "Ошибка при получении account по ID:" << query.lastError().text(); + return acc; + } + + if (query.next()) { + acc.id = query.value("id").toInt(); + acc.deviceId = query.value("device_id").toString(); + acc.appName = query.value("app_name").toString(); + acc.cardName = query.value("card_name").toString(); + acc.amount = query.value("amount").toDouble(); + acc.updateTime = QDateTime::fromString(query.value("update_time").toString(), Qt::ISODate); + acc.status = query.value("status").toString(); + acc.description = query.value("description").toString(); + } + + return acc; +} diff --git a/database/dao/AccountDAO.h b/database/dao/AccountDAO.h index 69c98bc..07a2e5b 100644 --- a/database/dao/AccountDAO.h +++ b/database/dao/AccountDAO.h @@ -8,4 +8,6 @@ public: [[nodiscard]] static bool deleteAccount(int id); [[nodiscard]] static QList getAccounts(const QString &deviceId, const QString &appName); + + [[nodiscard]] static AccountInfo getAccountById(int accountId); }; diff --git a/database/dao/DeviceDAO.cpp b/database/dao/DeviceDAO.cpp index 264985d..59d608c 100644 --- a/database/dao/DeviceDAO.cpp +++ b/database/dao/DeviceDAO.cpp @@ -4,12 +4,19 @@ #include "DatabaseManager.h" #include "db/DeviceInfo.h" -QList DeviceDAO::getAllDevices() { +QList DeviceDAO::getAllDevices(const bool online) { QList devices; QSqlQuery query(DatabaseManager::instance().database()); - query.prepare( - "SELECT id, status, name, android, screen_width, screen_height, battery, update_time, image FROM devices WHERE image IS NOT NULL AND status = 'device'"); + QString sql = R"( + SELECT id, status, name, android, screen_width, screen_height, battery, update_time, image + FROM devices + WHERE image IS NOT NULL + )"; + if (online) { + sql += " AND status = 'device'"; + } + query.prepare(sql); if (!query.exec()) { qWarning() << "Ошибка при получении списка устройств:" << query.lastError().text(); diff --git a/database/dao/DeviceDAO.h b/database/dao/DeviceDAO.h index a4dbf49..58d8bb4 100644 --- a/database/dao/DeviceDAO.h +++ b/database/dao/DeviceDAO.h @@ -10,5 +10,5 @@ public: [[nodiscard]] static bool updateImage(const QString &deviceId, const QByteArray &imageData); - [[nodiscard]] static QList getAllDevices(); + [[nodiscard]] static QList getAllDevices(bool online); }; diff --git a/views/AccountWindow.cpp b/views/AccountWindow.cpp new file mode 100644 index 0000000..0a64189 --- /dev/null +++ b/views/AccountWindow.cpp @@ -0,0 +1,100 @@ +#include "AccountWindow.h" + +#include +#include +#include +#include +#include + +#include "TransactionWindow.h" +#include "dao/AccountDAO.h" +#include "dao/DeviceDAO.h" + +AccountWindow::AccountWindow(QWidget *parent): QWidget(parent) { + auto *mainLayout = new QVBoxLayout(this); + + // Скролл-область + auto *scrollArea = new QScrollArea(this); + scrollArea->setWidgetResizable(true); + + // Виджет-контейнер, в котором будет layout с таблицами и лейблами + auto *contentWidget = new QWidget; + auto *layout = new QVBoxLayout(contentWidget); + + // Убираем отступы и рамку + contentWidget->setContentsMargins(0, 0, 0, 0); // Убираем отступы + layout->setContentsMargins(0, 0, 0, 0); // Убираем отступы у layout + scrollArea->setStyleSheet("border: none;"); // Убираем рамку + layout->setAlignment(Qt::AlignTop); + + QList devices = DeviceDAO::getAllDevices(false); + for (int i = 0; i < devices.size(); ++i) { + DeviceInfo device = devices[i]; + QList accounts = AccountDAO::getAccounts(device.id, "bank"); + // Текст перед первой таблицей + QString title = QString("%1 [%2] | %3").arg(device.name, device.id, device.status); + QLabel *label = new QLabel(title, this); + layout->addWidget(label); + + if (accounts.length() > 0) { + QTableWidget *tableWidget = new QTableWidget(this); + tableWidget->setStyleSheet("QTableWidget {" + "border: 1px solid grey;" + "}"); + + tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); + connect(tableWidget, &QTableWidget::itemDoubleClicked, this, &AccountWindow::onItemDoubleClicked); + + tableWidget->setRowCount(accounts.length()); // Устанавливаем количество строк + tableWidget->setColumnCount(5); // Устанавливаем количество столбцов + tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + tableWidget->setHorizontalHeaderLabels({"Карта", "Сумма", "Время обновления", "Описание", "Статус"}); + tableWidget->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter); + int totalHeight = tableWidget->horizontalHeader()->height(); + + + for (int k = 0; k < accounts.size(); ++k) { + AccountInfo account = accounts[k]; + QTableWidgetItem *item = new QTableWidgetItem("*" + account.cardName); + item->setData(Qt::UserRole, account.id); + tableWidget->setItem(k, 0, item); + tableWidget->setItem(k, 1, new QTableWidgetItem(QString::number(account.amount))); + tableWidget->setItem(k, 2, new QTableWidgetItem(account.updateTime.toString("dd.MM.yyyy HH:mm:ss"))); + tableWidget->setItem(k, 3, new QTableWidgetItem(account.description)); + tableWidget->setItem(k, 4, new QTableWidgetItem(account.status)); + totalHeight += tableWidget->rowHeight(k); + } + tableWidget->setFixedHeight(totalHeight + 2); + layout->addWidget(tableWidget); + } + + if (i < devices.size() - 1) { + QFrame *separator = new QFrame(this); + separator->setFrameShape(QFrame::VLine); + separator->setStyleSheet("background-color: black;"); // Цвет разделителя + separator->setFixedHeight(1); + separator->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + layout->addWidget(separator); + } + } + + // Добавим вертикальный Spacer в конец, чтобы прокручиваемая область не растягивала контент + QSpacerItem *spacer = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding); + layout->addItem(spacer); + + contentWidget->setLayout(layout); + scrollArea->setWidget(contentWidget); + mainLayout->addWidget(scrollArea); + setLayout(mainLayout); + setWindowTitle("Таблица устройств"); +} + +void AccountWindow::onItemDoubleClicked(QTableWidgetItem *item) { + int column = item->column(); + if (column == 0) { + int accountId = item->data(Qt::UserRole).toInt(); + qDebug() << accountId; + TransactionWindow *detailWindow = new TransactionWindow(accountId, this); + detailWindow->exec(); // Ожидаем закрытия окна + } +} diff --git a/views/AccountWindow.h b/views/AccountWindow.h new file mode 100644 index 0000000..ffd93d0 --- /dev/null +++ b/views/AccountWindow.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +class AccountWindow final : public QWidget { + Q_OBJECT + +public: + explicit AccountWindow(QWidget *parent = nullptr); + +private: + void onItemDoubleClicked(QTableWidgetItem *item); +}; diff --git a/views/MainWindow.cpp b/views/MainWindow.cpp index e486a6c..de0a84b 100644 --- a/views/MainWindow.cpp +++ b/views/MainWindow.cpp @@ -1,8 +1,10 @@ #include "MainWindow.h" +#include "AccountWindow.h" #include #include #include +#include #include "db/DeviceInfo.h" #include "DeviceWidget.h" @@ -10,64 +12,102 @@ #include "widget/FlowLayout.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { - // QPushButton* btn = new QPushButton("Hello Qt6!", this); - // setCentralWidget(btn); + // Инициализируем таймер обновления + timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, &MainWindow::loadDevices); - auto* scrollArea = new QScrollArea(this); - auto* contentWidget = new QWidget; - flowLayout = new FlowLayout(contentWidget); + stackedWidget = new QStackedWidget(this); + setCentralWidget(stackedWidget); + createDevicePage(); + stackedWidget->addWidget(devicePage); + // stackedWidget->setCurrentWidget(devicePage); FIXME - - contentWidget->setContentsMargins(0, 0, 0, 0); // Убираем отступы у контейнера - flowLayout->setContentsMargins(0, 0, 0, 0); // Убираем отступы у layout - flowLayout->setSpacing(0); - - - QVector devices = DeviceDAO::getAllDevices(); - for (const auto& device : devices) { - auto* deviceWidget = new DeviceWidget(device); // Создаем виджет, используя первый элемент из списка devices - deviceWidget->setFixedWidth(220); - deviceWidget->setFixedHeight(400); - - deviceWidget->setContentsMargins(0, 0, 0, 0); - flowLayout->addWidget(deviceWidget); - } + // FIXME переделать на ленивое создание + accountPage = new AccountWindow(this); + stackedWidget->addWidget(accountPage); + stackedWidget->setCurrentWidget(accountPage); resize(800, 600); + // Работа с меню + QMenu *menu = menuBar()->addMenu("Меню"); + auto *devicesPageAction = new QAction("Активные устройства", this); + auto *accountPageAction = new QAction("Аккаунты", this); + menu->addAction(devicesPageAction); + menu->addAction(accountPageAction); + + connect(devicesPageAction, &QAction::triggered, this, [=]() { + if (!devicePage) { + createDevicePage(); + } + stackedWidget->setCurrentWidget(devicePage); + }); + + connect(accountPageAction, &QAction::triggered, this, [=]() { + stackedWidget->setCurrentWidget(accountPage); + deleteDevicePage(); // освобождаем ресурсы + }); +} + + +void MainWindow::createDevicePage() { + if (devicePage) { + return; + } + + auto *scrollArea = new QScrollArea(this); + auto *contentWidget = new QWidget; + flowLayout = new FlowLayout(contentWidget); + flowLayout->setContentsMargins(0, 0, 0, 0); + flowLayout->setSpacing(0); + + QVector devices = DeviceDAO::getAllDevices(true); + for (const auto &device: devices) { + auto *deviceWidget = new DeviceWidget(device); + deviceWidget->setFixedSize(220, 400); + flowLayout->addWidget(deviceWidget); + } + contentWidget->setLayout(flowLayout); scrollArea->setWidget(contentWidget); scrollArea->setWidgetResizable(true); - setCentralWidget(scrollArea); - // Меню - QMenu* menu = menuBar()->addMenu("Навигация"); + devicePage = scrollArea; + stackedWidget->addWidget(devicePage); - QAction* actionPage1 = new QAction("Окно 1", this); - QAction* actionPage2 = new QAction("Окно 2", this); - - menu->addAction(actionPage1); - menu->addAction(actionPage2); - - timer = new QTimer(this); - connect(timer, &QTimer::timeout, this, &MainWindow::loadDevices); - timer->start(1000); // каждые 1 сек + timer->start(1000); } - -void MainWindow::loadDevices() const { - const auto devices = DeviceDAO::getAllDevices(); - - // Очистить старые виджеты - QLayoutItem* item; - while ((item = flowLayout->takeAt(0)) != nullptr) { - delete item->widget(); // Удаляем виджет - delete item; // Удаляем сам элемент из layout +void MainWindow::deleteDevicePage() { + if (!devicePage) { + return; } + timer->stop(); + + stackedWidget->removeWidget(devicePage); + devicePage->deleteLater(); + devicePage = nullptr; + flowLayout = nullptr; +} + +/** + * Однако, в нем вы вызываете delete для элементов, что изменяет память. + * Для того, чтобы не нарушать константность, этот метод должен быть неконстантным (без const в сигнатуре). + */ +void MainWindow::loadDevices() { + const auto devices = DeviceDAO::getAllDevices(true); + // Очистить старые виджеты + QLayoutItem *item; + while ((item = flowLayout->takeAt(0)) != nullptr) { + if (item->widget()) { + delete item->widget(); // Удаляем виджет + } + delete item; // Удаляем сам элемент из layout + } // Добавляем новые виджеты для каждого устройства - for (const auto& device : devices) { + for (const auto &device: devices) { flowLayout->addWidget(new DeviceWidget(device)); } } diff --git a/views/MainWindow.h b/views/MainWindow.h index 29205d5..685b957 100644 --- a/views/MainWindow.h +++ b/views/MainWindow.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include #include "db/DeviceInfo.h" #include "widget/FlowLayout.h" @@ -9,10 +10,17 @@ class MainWindow final : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = nullptr); - void loadDevices() const; + void loadDevices(); private: QWidget* central; FlowLayout* flowLayout; QTimer* timer; + QStackedWidget* stackedWidget; + + QWidget* devicePage = nullptr; + QWidget* accountPage = nullptr; + + void createDevicePage(); + void deleteDevicePage(); }; diff --git a/views/TransactionWindow.cpp b/views/TransactionWindow.cpp new file mode 100644 index 0000000..a5e3766 --- /dev/null +++ b/views/TransactionWindow.cpp @@ -0,0 +1,21 @@ +#include "TransactionWindow.h" +#include +#include + +#include "dao/AccountDAO.h" + +TransactionWindow::TransactionWindow(const int accountId, QWidget *parent): QDialog(parent) { + setWindowTitle("Транзакции"); + setMinimumSize(300, 200); // минимальный размер + setMaximumSize(800, 400); // максимальный размер + + auto *layout = new QVBoxLayout(this); + layout->setAlignment(Qt::AlignCenter); + + AccountInfo account = AccountDAO::getAccountById(accountId); + + QLabel *label = new QLabel(QString("*%1 | %2").arg(account.cardName, account.description), this); + layout->addWidget(label); + + setModal(true); +} diff --git a/views/TransactionWindow.h b/views/TransactionWindow.h new file mode 100644 index 0000000..ab49f47 --- /dev/null +++ b/views/TransactionWindow.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +class TransactionWindow final : public QDialog { + Q_OBJECT + +public: + explicit TransactionWindow(int accountId, QWidget *parent = nullptr); +};