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); +};