From 4a52926be96c04ad526eb9e016ed404e8a376ada Mon Sep 17 00:00:00 2001 From: "Denis V. Dedkov" Date: Mon, 10 Apr 2023 19:24:25 +0200 Subject: [PATCH] Choice model settings was added --- CMakeLists.txt | 10 +-- beerlog_ru_RU.ts | 36 +++++++++-- main.cpp | 14 ++-- models/abstractmodel.cpp | 41 ------------ models/abstractmodel.h | 34 ---------- models/basemodel.cpp | 90 ++++++++++++++++++++++++++ models/basemodel.h | 44 +++++++++++++ models/ordersmodel.cpp | 15 +++++ models/ordersmodel.h | 17 +++++ models/summarymodel.cpp | 39 ------------ models/summarymodel.h | 28 -------- models/usersmodel.cpp | 20 +----- models/usersmodel.h | 7 +- qml/Views/OrdersView.qml | 57 ++++++++++++++++- qml/Views/ProductsView.qml | 106 +++++++++++++++++++++++++++++++ qml/Views/SettingsView.qml | 78 ++++++++++++++++------- qml/main.qml | 31 +++++++-- qml/qml.qrc | 1 + services/beerservice.cpp | 22 ++++++- services/beerservice.h | 13 +++- services/settingsservice.cpp | 32 ++++++++-- services/settingsservice.h | 11 +++- viewmodels/ordersviewmodel.cpp | 72 +++++++++++++++++++++ viewmodels/ordersviewmodel.h | 43 +++++++++++++ viewmodels/productsviewmodel.cpp | 78 +++++++++++++++++++++++ viewmodels/productsviewmodel.h | 38 +++++++++++ viewmodels/settingsviewmodel.cpp | 57 +++++++++++++++++ viewmodels/settingsviewmodel.h | 47 ++++++++++++++ viewmodels/storesviewmodel.cpp | 38 +++++++++++ viewmodels/storesviewmodel.h | 36 +++++++++++ viewmodels/usersviewmodel.cpp | 10 +-- 31 files changed, 938 insertions(+), 227 deletions(-) delete mode 100644 models/abstractmodel.cpp delete mode 100644 models/abstractmodel.h create mode 100644 models/basemodel.cpp create mode 100644 models/basemodel.h create mode 100644 models/ordersmodel.cpp create mode 100644 models/ordersmodel.h delete mode 100644 models/summarymodel.cpp delete mode 100644 models/summarymodel.h create mode 100644 qml/Views/ProductsView.qml create mode 100644 viewmodels/ordersviewmodel.cpp create mode 100644 viewmodels/ordersviewmodel.h create mode 100644 viewmodels/productsviewmodel.cpp create mode 100644 viewmodels/productsviewmodel.h create mode 100644 viewmodels/settingsviewmodel.cpp create mode 100644 viewmodels/settingsviewmodel.h create mode 100644 viewmodels/storesviewmodel.cpp create mode 100644 viewmodels/storesviewmodel.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d9c235..ead4efc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,10 +17,14 @@ set(TS_FILES beerlog_ru_RU.ts) set(PROJECT_SOURCES main.cpp qml/qml.qrc - models/abstractmodel.h models/abstractmodel.cpp - models/summarymodel.h models/summarymodel.cpp + models/basemodel.h models/basemodel.cpp + models/ordersmodel.h models/ordersmodel.cpp models/usersmodel.h models/usersmodel.cpp viewmodels/usersviewmodel.h viewmodels/usersviewmodel.cpp + viewmodels/storesviewmodel.h viewmodels/storesviewmodel.cpp + viewmodels/productsviewmodel.h viewmodels/productsviewmodel.cpp + viewmodels/ordersviewmodel.h viewmodels/ordersviewmodel.cpp + viewmodels/settingsviewmodel.h viewmodels/settingsviewmodel.cpp services/beerservice.h services/beerservice.cpp services/settingsservice.h services/settingsservice.cpp ${TS_FILES} @@ -55,8 +59,6 @@ else() qt5_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES}) endif() -qt_add_translations(beerlog TS_FILES beerlog_ru_RU.ts) - target_link_libraries(beerlog PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Quick Qt${QT_VERSION_MAJOR}::WebSockets) diff --git a/beerlog_ru_RU.ts b/beerlog_ru_RU.ts index 2dd95d2..81c3c85 100644 --- a/beerlog_ru_RU.ts +++ b/beerlog_ru_RU.ts @@ -14,6 +14,27 @@ Не в сети + + OrdersView + + + Summary: %1 + + + + + ProductsView + + + Summary: %1 + + + + + Order + + + SettingsView @@ -40,24 +61,29 @@ - + BeerLog v0.1 BeerLog v1.0.0 - + + Orders + + + + Settings Настройки - - + + Quit Выход - + Realy quit the application? Действительно выйти из приложения? diff --git a/main.cpp b/main.cpp index d0be815..1e2b52e 100644 --- a/main.cpp +++ b/main.cpp @@ -6,6 +6,11 @@ #include #include "viewmodels/usersviewmodel.h" +#include "viewmodels/productsviewmodel.h" +#include "viewmodels/ordersviewmodel.h" +#include "viewmodels/storesviewmodel.h" +#include "viewmodels/settingsviewmodel.h" + #include "services/beerservice.h" #include "services/settingsservice.h" @@ -17,11 +22,6 @@ int main(int argc, char *argv[]) QGuiApplication app(argc, argv); QTranslator translator; - - if (!translator.load("qt_ru.qm", "qrc:/")) { - qWarning() << "Cant load"; - } - const QStringList uiLanguages = QLocale::system().uiLanguages(); for (const QString &locale : uiLanguages) { const QString baseName = "beerlog_" + QLocale(locale).name(); @@ -45,6 +45,10 @@ int main(int argc, char *argv[]) engine.rootContext()->setContextProperty("settingsService", SettingsService::instance()); qmlRegisterType("ru.ded.beerlog", 1, 0, "UsersViewModel"); + qmlRegisterType("ru.ded.beerlog", 1, 0, "ProductsViewModel"); + qmlRegisterType("ru.ded.beerlog", 1, 0, "OrdersViewModel"); + qmlRegisterType("ru.ded.beerlog", 1, 0, "StoresViewModel"); + qmlRegisterType("ru.ded.beerlog", 1, 0, "SettingsViewModel"); engine.load(url); diff --git a/models/abstractmodel.cpp b/models/abstractmodel.cpp deleted file mode 100644 index f73ccce..0000000 --- a/models/abstractmodel.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include "abstractmodel.h" - -#include "services/beerservice.h" - -AbstractModel::AbstractModel(QObject *parent) - : QObject{parent} -{ -} - -void AbstractModel::created(const QVariant &data) -{ - modified(data); -} - -void AbstractModel::modified(const QVariant &data) -{ - QVariantMap d = data.toMap(); - m_data[d.value("id").toString()] = d; - - emit dataChanged(); -} - -void AbstractModel::deleted(const QVariant &data) -{ - QString id = data.toString(); - m_data.remove(id); - - emit dataChanged(); -} - -void AbstractModel::received(const QVariant &data) -{ - m_data = data.toMap(); - - emit dataChanged(); -} - -BeerService *AbstractModel::service() const -{ - return BeerService::instance(); -} diff --git a/models/abstractmodel.h b/models/abstractmodel.h deleted file mode 100644 index b41ae67..0000000 --- a/models/abstractmodel.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef ABSTRACTMODEL_H -#define ABSTRACTMODEL_H - -#include -#include - -class BeerService; -class AbstractModel : public QObject -{ - Q_OBJECT - - Q_PROPERTY(QString entity READ entity CONSTANT) - -public: - explicit AbstractModel(QObject *parent = nullptr); - - virtual QString entity() const = 0; - -public slots: - void created(const QVariant &data); - void modified(const QVariant &data); - void deleted(const QVariant &data); - void received(const QVariant &data); - -signals: - void dataChanged(); - -protected: - BeerService *service() const; - - QVariantMap m_data; -}; - -#endif // ABSTRACTMODEL_H diff --git a/models/basemodel.cpp b/models/basemodel.cpp new file mode 100644 index 0000000..14a9f68 --- /dev/null +++ b/models/basemodel.cpp @@ -0,0 +1,90 @@ +#include "basemodel.h" + +#include "services/beerservice.h" + +BaseModel::BaseModel(const QString &entity, QObject *parent) : QObject{parent}, + m_entity(entity) +{ + Q_ASSERT(!m_entity.isEmpty()); + + service()->connectListener(this); + service()->sendCommand(m_entity, BeerService::ActionGet); +} + +BaseModel::~BaseModel() +{ + service()->removeListener(this); +} + +QString BaseModel::entity() const +{ + return m_entity; +} + +QVariantList BaseModel::items() const +{ + return m_data.values(); +} + +QVariantMap BaseModel::item(const QString &itemId) const +{ + return m_data.value(itemId).toMap(); +} + +QVariant BaseModel::itemProperty(const QString &itemId, const QString &propertyName, const QVariant &def) const +{ + return item(itemId).value(propertyName, def); +} + +void BaseModel::addItem(const QVariantMap &item) const +{ + service()->sendCommand(entity(), BeerService::ActionAdd, item); +} + +void BaseModel::deleteItem(const QString &itemId) const +{ + service()->sendCommand(entity(), BeerService::ActionDelete, QVariantMap { { "id", itemId } }); +} + +void BaseModel::modifyItem(const QString &itemId, const QVariantMap &properties) const +{ + QVariantMap item = this->item(itemId); + for (auto it = properties.constBegin(); it != properties.constEnd(); ++it) { + item[it.key()] = it.value(); + } + + service()->sendCommand(entity(), BeerService::ActionModify, item); +} + +void BaseModel::created(const QVariant &data) +{ + modified(data); +} + +void BaseModel::modified(const QVariant &data) +{ + QVariantMap d = data.toMap(); + m_data[d.value("id").toString()] = d; + + emit dataChanged(); +} + +void BaseModel::deleted(const QVariant &data) +{ + QString id = data.toString(); + m_data.remove(id); + + emit dataChanged(); +} + +void BaseModel::received(const QVariant &data) +{ + m_data = data.toMap(); + + emit dataChanged(); +} + +BeerService *BaseModel::service() const +{ + return BeerService::instance(); +} diff --git a/models/basemodel.h b/models/basemodel.h new file mode 100644 index 0000000..0c50afb --- /dev/null +++ b/models/basemodel.h @@ -0,0 +1,44 @@ +#ifndef BASEMODEL_H +#define BASEMODEL_H + +#include +#include + +class BeerService; +class BaseModel : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString entity READ entity CONSTANT) + +public: + explicit BaseModel(const QString &entity, QObject *parent = nullptr); + virtual ~BaseModel(); + + QString entity() const; + + QVariantList items() const; + QVariantMap item(const QString &itemId) const; + QVariant itemProperty(const QString &itemId, const QString &propertyName, const QVariant &def = QVariant{}) const; + + void addItem(const QVariantMap &item) const; + void deleteItem(const QString &itemId) const; + void modifyItem(const QString &itemId, const QVariantMap &properties) const; + +public slots: + void created(const QVariant &data); + void modified(const QVariant &data); + void deleted(const QVariant &data); + void received(const QVariant &data); + +signals: + void dataChanged(); + +private: + BeerService *service() const; + + QVariantMap m_data; + QString m_entity; +}; + +#endif // BASEMODEL_H diff --git a/models/ordersmodel.cpp b/models/ordersmodel.cpp new file mode 100644 index 0000000..aa51830 --- /dev/null +++ b/models/ordersmodel.cpp @@ -0,0 +1,15 @@ +#include "ordersmodel.h" + +OrdersModel::OrdersModel(QObject *parent) : BaseModel { "orders", parent } +{ +} + +QVariantList OrdersModel::orders() const +{ + return items(); +} + +void OrdersModel::submitOrder(const QVariantMap &order) const +{ + addItem(order); +} diff --git a/models/ordersmodel.h b/models/ordersmodel.h new file mode 100644 index 0000000..84f26ae --- /dev/null +++ b/models/ordersmodel.h @@ -0,0 +1,17 @@ +#ifndef ORDERSMODEL_H +#define ORDERSMODEL_H + +#include "basemodel.h" + +class OrdersModel : public BaseModel +{ + Q_OBJECT + +public: + explicit OrdersModel(QObject *parent = nullptr); + + QVariantList orders() const; + void submitOrder(const QVariantMap &order) const; +}; + +#endif // ORDERSMODEL_H diff --git a/models/summarymodel.cpp b/models/summarymodel.cpp deleted file mode 100644 index 71c26be..0000000 --- a/models/summarymodel.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include "summarymodel.h" - -QVariantList SummaryModel::items() const -{ - return m_items.values(); -} - -float SummaryModel::sum() const -{ - float res = 0.0; - for (auto it = m_items.constBegin(); it != m_items.constEnd(); ++it) { - QVariantMap item = it.value().toMap(); - int count = item.value("count", 0).toInt(); - float price = item.value("cost", 0.0).toFloat(); - res += count * price; - } - return res; -} - -void SummaryModel::setItemCount(QVariantMap item, int count) -{ - QString id = item.value("id", QString()).toString(); - - if (count) { - item["count"] = count; - m_items[id] = item; - } else { - m_items.remove(id); - } - - emit itemsChanged(); -} - -void SummaryModel::clear() -{ - m_items.clear(); - - emit itemsChanged(); -} diff --git a/models/summarymodel.h b/models/summarymodel.h deleted file mode 100644 index 8dd6add..0000000 --- a/models/summarymodel.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef SUMMARYMODEL_H -#define SUMMARYMODEL_H - -#include -#include - -class SummaryModel : public QObject -{ - Q_OBJECT - - Q_PROPERTY(QVariantList items READ items NOTIFY itemsChanged) - Q_PROPERTY(float sum READ sum NOTIFY itemsChanged) - -public: - QVariantList items() const; - float sum() const; - - Q_INVOKABLE void setItemCount(QVariantMap item, int count); - Q_INVOKABLE void clear(); - -signals: - void itemsChanged(); - -private: - QVariantMap m_items; -}; - -#endif // SUMMARYMODEL_H diff --git a/models/usersmodel.cpp b/models/usersmodel.cpp index 197bb9d..f8f799e 100644 --- a/models/usersmodel.cpp +++ b/models/usersmodel.cpp @@ -1,24 +1,13 @@ #include "usersmodel.h" -#include "services/beerservice.h" - namespace Keys { -constexpr auto Users = "users"; constexpr auto Name = "name"; } -UsersModel::UsersModel(QObject *parent) - : AbstractModel{parent} +UsersModel::UsersModel(QObject *parent) : BaseModel{ "users", parent } { - service()->connectListener(this); - service()->sendCommand(Keys::Users, "get"); -} - -QString UsersModel::entity() const -{ - return Keys::Users; } void UsersModel::connected(const QVariant &data) @@ -33,10 +22,5 @@ void UsersModel::disconnected(const QVariant &data) QVariantList UsersModel::users() const { - return m_data.values(); -} - -QString UsersModel::userName(const QString &userId) const -{ - return m_data.value(userId).toMap().value(Keys::Name).toString(); + return items(); } diff --git a/models/usersmodel.h b/models/usersmodel.h index 382de44..8c14b48 100644 --- a/models/usersmodel.h +++ b/models/usersmodel.h @@ -4,19 +4,16 @@ #include #include -#include "models/abstractmodel.h" +#include "models/basemodel.h" -class UsersModel : public AbstractModel +class UsersModel : public BaseModel { Q_OBJECT public: explicit UsersModel(QObject *parent = nullptr); - QString entity() const override; - QVariantList users() const; - QString userName(const QString &userId) const; public slots: void connected(const QVariant &data); diff --git a/qml/Views/OrdersView.qml b/qml/Views/OrdersView.qml index 9dec0d1..9370cac 100644 --- a/qml/Views/OrdersView.qml +++ b/qml/Views/OrdersView.qml @@ -1,12 +1,63 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 + +import ru.ded.beerlog 1.0 Page { - Label { - anchors.centerIn: parent + title: qsTr("Orders") - text: "Orders" + OrdersViewModel { + id: ordersModel } + ListView { + id: ordersList + + anchors.fill: parent + anchors.margins: 10 + + model: ordersModel + + section.criteria: ViewSection.FullString + section.property: "date" + section.delegate: Label { + text: section + font.bold: true + } + + delegate: Column { + width: ordersList.width + height: productsList.heigt + + ListView { + id: productsList + + width: ordersList.width + height: contentHeight + + model: products + + header: Label { + padding: 10 + font.bold: true + text: "%1 (%2), %3".arg(userName).arg(storeName).arg(time.toLocaleTimeString(Qt.locale(), Locale.ShortFormat)) + } + + delegate: Label { + width: ordersList.width + leftPadding: 20 + + text: "%1 x%2, %3".arg(modelData.product).arg(modelData.quantity.toLocaleString(Qt.locale())).arg(modelData.price.toLocaleCurrencyString(Qt.locale())) + } + + footer: Label { + padding: 10 + font.bold: true + text: qsTr("Summary: %1").arg(amount.toLocaleCurrencyString(Qt.locale())) + } + } + } + } } diff --git a/qml/Views/ProductsView.qml b/qml/Views/ProductsView.qml new file mode 100644 index 0000000..a40e38c --- /dev/null +++ b/qml/Views/ProductsView.qml @@ -0,0 +1,106 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 + +import ru.ded.beerlog 1.0 + +Page { + + ProductsViewModel { + id: productsModel + } + + RowLayout { + anchors.fill: parent + + ListView { + id: productsList + + Layout.fillWidth: true + Layout.preferredHeight: parent.height + + model: productsModel.products + + delegate: ItemDelegate { + width: productsList.width + + text: modelData.name + height: spinbox.height + + SpinBox { + id: spinbox + + width: 150 + from: 0 + to: 100 * 100 + stepSize: 50 + anchors.right: parent.right + editable: true + + property int decimals: 1 + property real realValue: value / 100 + + validator: DoubleValidator { + bottom: Math.min(spinbox.from, spinbox.to) + top: Math.max(spinbox.from, spinbox.to) + } + + textFromValue: function(value, locale) { + return value === 0 ? "" : Number(value / 100).toLocaleString(locale, 'f', spinbox.decimals) + } + + valueFromText: function(text, locale) { + return Number.fromLocaleString(locale, text) * 100 + } + + onRealValueChanged: productsModel.setOrderValue(modelData.id, realValue) + } + } + + function reload() { + model = undefined + model = productsModel.products + } + } + + ListView { + id: orderList + + Layout.minimumWidth: parent.width * 0.3 + Layout.preferredHeight: parent.height + + model: productsModel.order + + delegate: ItemDelegate { + width: orderList.width + + text: "%1 x%2".arg(modelData.name).arg(modelData.count.toLocaleString(Qt.locale())) + } + + footer: Column { + width: orderList.width + + Label { + anchors.right: parent.right + anchors.margins: 10 + + text: qsTr("Summary: %1").arg(productsModel.orderSum.toLocaleCurrencyString(Qt.locale())) + } + + Button { + anchors.right: parent.right + anchors.margins: 10 + + text: qsTr("Order") + + enabled: productsModel.orderSum > 0 + + onClicked: { + productsModel.submitOrder() + productsList.reload() + } + } + } + } + } +} diff --git a/qml/Views/SettingsView.qml b/qml/Views/SettingsView.qml index ff23454..629ea37 100644 --- a/qml/Views/SettingsView.qml +++ b/qml/Views/SettingsView.qml @@ -1,24 +1,20 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 import Components 1.0 +import ru.ded.beerlog 1.0 Page { id: root title: qsTr("Settings") - ListModel { + property var controls: { + "text": textComponent, + "choice": choiceComponent + } + + SettingsViewModel { id: settingsModel - - ListElement { - title: qsTr("BeerLog service address") - name: "serverAddress" - } - - ListElement { - title: qsTr("Selected user id") - name: "selectedUserId" - } } ListView { @@ -35,6 +31,8 @@ Page { onClicked: { inputDialog.title = model.title inputDialog.name = model.name + inputDialog.control = controls[model.control] + inputDialog.choiceModel = model.choiceModel inputDialog.open() } } @@ -44,6 +42,8 @@ Page { id: inputDialog property string name: "" + property var choiceModel: undefined + property alias control: editor.sourceComponent width: root.width * 0.8 @@ -53,22 +53,54 @@ Page { modal: true standardButtons: Dialog.Ok | Dialog.Cancel - Column { - spacing: 20 + Loader { + id: editor + + property var value: item && item.value + anchors.fill: parent - - TextField { - id: textField - - width: parent.width - inputMethodHints: Qt.ImhNoAutoUppercase - placeholderText: inputDialog.title - text: settingsService[inputDialog.name] || "" - } } onAccepted: { - settingsService[inputDialog.name] = textField.text + settingsService[inputDialog.name] = editor.value + } + } + + Component { + id: textComponent + + TextField { + id: textField + + property alias value: textField.text + + inputMethodHints: Qt.ImhNoAutoUppercase + text: settingsService[inputDialog.name] || "" + } + } + + ButtonGroup { + id: group + } + + Component { + id: choiceComponent + + Column { + + property var value: group.checkedButton && group.checkedButton.valueId + + Repeater { + model: inputDialog.choiceModel + + delegate: RadioDelegate { + property var valueId: modelData.id + text: modelData.name + width: parent.width + checked: settingsService[inputDialog.name] === valueId + ButtonGroup.group: group + } + } } } } diff --git a/qml/main.qml b/qml/main.qml index a273d35..a320826 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -26,6 +26,7 @@ ApplicationWindow { Label { text: stackView.currentItem.title || usersModel.selectedUserName Layout.fillWidth: true + Layout.minimumWidth: 100 horizontalAlignment: Qt.AlignCenter MouseArea { @@ -55,24 +56,46 @@ ApplicationWindow { } } } + + Menu { + id: storesMenu + + StoresViewModel { + id: storesModel + } + + Repeater { + model: storesModel.stores + + MenuItem { + text: modelData.name + + onClicked: { + storesModel.selectedStore = modelData.id + } + } + } + } } MainMenu { id: mainMenu readonly property var actions: { + "orders": () => { stackView.openPage("Views/OrdersView.qml") }, "settings": () => { stackView.openPage("Views/SettingsView.qml") }, "quit": () => { Qt.quit() } } - width: parent.width * 0.66 - height: parent.height - logo: "qrc:/logo.png" appName: qsTr("BeerLog v0.1") connected: beerService.connected model: ListModel { + ListElement { + title: qsTr("Orders") + action: "orders" + } ListElement { title: qsTr("Settings") action: "settings" @@ -88,7 +111,7 @@ ApplicationWindow { StackView { id: stackView - initialItem: "Views/OrdersView.qml" + initialItem: "Views/ProductsView.qml" anchors.fill: parent function openPage(page) { diff --git a/qml/qml.qrc b/qml/qml.qrc index ca1e243..52af8c3 100644 --- a/qml/qml.qrc +++ b/qml/qml.qrc @@ -11,5 +11,6 @@ Components/SubtitledItemDelegate.qml Components/qmldir qt_ru.qm + Views/ProductsView.qml diff --git a/services/beerservice.cpp b/services/beerservice.cpp index 823fcbb..b36c328 100644 --- a/services/beerservice.cpp +++ b/services/beerservice.cpp @@ -12,6 +12,13 @@ BeerService::BeerService() : QObject{nullptr} { + m_actions = { + { ActionGet, "get" }, + { ActionAdd, "add" }, + { ActionDelete, "del" }, + { ActionModify, "mod" } + }; + restoreStash(); connect(&m_socket, &QWebSocket::textMessageReceived, this, [this](QString message) { @@ -31,7 +38,7 @@ BeerService::BeerService() } }); - connect(&m_socket, QOverload::of(&QWebSocket::error), this, [this](QAbstractSocket::SocketError error) { + connect(&m_socket, &QWebSocket::errorOccurred, this, [this](QAbstractSocket::SocketError error) { qInfo() << error << m_socket.errorString(); }); @@ -60,11 +67,13 @@ SettingsService *BeerService::settings() const return SettingsService::instance(); } -void BeerService::sendCommand(const QString &entity, const QString &action, const QVariantMap &data) +void BeerService::sendCommand(const QString &entity, Action action, const QVariantMap &data) { + Q_ASSERT(action != ActionUndefined); + sendCommand(QVariantMap { { "entity", entity }, - { "action", action }, + { "action", m_actions[action] }, { "data", data } }); } @@ -75,6 +84,12 @@ void BeerService::connectListener(QObject *listener) m_listeners.insert(entity, listener); } +void BeerService::removeListener(QObject *listener) +{ + QString entity = listener->property("entity").toString(); + m_listeners.remove(entity, listener); +} + QString BeerService::stashFileName() const { return QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/command.stash"; @@ -102,6 +117,7 @@ void BeerService::restoreStash() QJsonDocument doc = QJsonDocument::fromJson(stash.readAll()); m_commandStash = doc.array().toVariantList(); stash.close(); + stash.remove(); } else { qWarning() << stash.errorString(); } diff --git a/services/beerservice.h b/services/beerservice.h index 6eb506f..d0ae8d3 100644 --- a/services/beerservice.h +++ b/services/beerservice.h @@ -18,8 +18,18 @@ public: return &i; } - void sendCommand(const QString &entity, const QString &action, const QVariantMap &data = QVariantMap()); + enum Action + { + ActionUndefined = 0, + ActionGet, + ActionAdd, + ActionDelete, + ActionModify + }; + + void sendCommand(const QString &entity, Action action, const QVariantMap &data = QVariantMap()); void connectListener(QObject *listener); + void removeListener(QObject *listener); signals: void connectedChanged(); @@ -42,6 +52,7 @@ private: QWebSocket m_socket; QVariantList m_commandStash; + QMap m_actions; }; #endif // BEERSERVICE_H diff --git a/services/settingsservice.cpp b/services/settingsservice.cpp index f0ba617..0a76a39 100644 --- a/services/settingsservice.cpp +++ b/services/settingsservice.cpp @@ -11,6 +11,7 @@ namespace Keys { constexpr auto ServerAddress = "server_address"; constexpr auto SelectedUser = "selected_user"; +constexpr auto SelectedStore = "selected_store"; } @@ -26,22 +27,45 @@ void SettingsService::setValue(const QString &key, const QVariant &value) QString SettingsService::serverAddress() const { - return m_settings.value(Keys::ServerAddress, Defaults::ServerAddress).toString(); + return value(Keys::ServerAddress, Defaults::ServerAddress).toString(); } void SettingsService::setServerAddress(const QString &address) { - m_settings.setValue(Keys::ServerAddress, address); + if (serverAddress() == address) { + return; + } + + setValue(Keys::ServerAddress, address); emit serverAddressChanged(); } QString SettingsService::selectedUserId() const { - return m_settings.value(Keys::SelectedUser, Defaults::GuestUserId).toString(); + return value(Keys::SelectedUser, Defaults::GuestUserId).toString(); } void SettingsService::setSelectedUserId(const QString &userId) { - m_settings.setValue(Keys::SelectedUser, userId); + if (selectedUserId() == userId) { + return; + } + + setValue(Keys::SelectedUser, userId); emit selectedUserIdChanged(); } + +QString SettingsService::selectedStoreId() const +{ + return value(Keys::SelectedStore).toString(); +} + +void SettingsService::setSelectedStoreId(const QString &storeId) +{ + if (selectedStoreId() == storeId) { + return; + } + + setValue(Keys::SelectedStore, storeId); + emit selectedStoreIdChanged(); +} diff --git a/services/settingsservice.h b/services/settingsservice.h index b0eac76..d2b40ec 100644 --- a/services/settingsservice.h +++ b/services/settingsservice.h @@ -10,6 +10,7 @@ class SettingsService : public QObject Q_PROPERTY(QString serverAddress READ serverAddress WRITE setServerAddress NOTIFY serverAddressChanged) Q_PROPERTY(QString selectedUserId READ selectedUserId WRITE setSelectedUserId NOTIFY selectedUserIdChanged) + Q_PROPERTY(QString selectedStoreId READ selectedStoreId WRITE setSelectedStoreId NOTIFY selectedStoreIdChanged) public: static SettingsService *instance() @@ -18,20 +19,24 @@ public: return &i; } - QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const; - void setValue(const QString &key, const QVariant &value); - QString serverAddress() const; void setServerAddress(const QString &address); QString selectedUserId() const; void setSelectedUserId(const QString &userId); + QString selectedStoreId() const; + void setSelectedStoreId(const QString &storeId); + signals: void serverAddressChanged(); void selectedUserIdChanged(); + void selectedStoreIdChanged(); private: + QVariant value(const QString &key, const QVariant &defaultValue = QVariant{}) const; + void setValue(const QString &key, const QVariant &value); + SettingsService() = default; ~SettingsService() = default; diff --git a/viewmodels/ordersviewmodel.cpp b/viewmodels/ordersviewmodel.cpp new file mode 100644 index 0000000..aa100bd --- /dev/null +++ b/viewmodels/ordersviewmodel.cpp @@ -0,0 +1,72 @@ +#include "ordersviewmodel.h" + +#include + +OrdersViewModel::OrdersViewModel(QObject *parent) + : QAbstractListModel{parent} +{ + connect(&m_ordersModel, &BaseModel::dataChanged, this, &OrdersViewModel::reload); + connect(&m_usersModel, &BaseModel::dataChanged, this, &OrdersViewModel::reload); + connect(&m_productsModel, &BaseModel::dataChanged, this, &OrdersViewModel::reload); + connect(&m_storesModel, &BaseModel::dataChanged, this, &OrdersViewModel::reload); + + reload(); +} + +int OrdersViewModel::rowCount(const QModelIndex &) const +{ + return m_model.count(); +} + +QVariant OrdersViewModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + return {}; + } + + return m_model.at(index.row()).toMap()[roleNames().value(role)]; +} + +QHash OrdersViewModel::roleNames() const +{ + return QHash { + { Roles::UserName, "userName" }, + { Roles::StoreName, "storeName" }, + { Roles::Date, "date" }, + { Roles::Time, "time"}, + { Roles::Amount, "amount" }, + { Roles::Products, "products" } + }; +} + +void OrdersViewModel::reload() +{ + beginResetModel(); + + m_model.clear(); + + for (const QVariant &vOrder : m_ordersModel.orders()) { + QVariantMap order = vOrder.toMap(); + QDateTime orderTime = QDateTime::fromSecsSinceEpoch(order.value("ts", 0).toDouble()); + order["date"] = orderTime.date(); + order["time"] = orderTime.time(); + order["userName"] = m_usersModel.itemProperty(order["userId"].toString(), "name").toString(); + order["storeName"] = m_storesModel.itemProperty(order["storeId"].toString(), "name").toString(); + + QVariantList prodModel; + for (const QVariant &prod : order["products"].toList()) { + QVariantMap product = prod.toMap(); + product["product"] = m_productsModel.itemProperty(product.value("productId").toString(), "name"); + prodModel << product; + } + order["products"] = prodModel; + + m_model << order; + } + + std::sort(m_model.begin(), m_model.end(), [](const QVariant &l, const QVariant &r) { + return l.toMap().value("ts").toDouble() < r.toMap().value("ts").toDouble(); + }); + + endResetModel(); +} diff --git a/viewmodels/ordersviewmodel.h b/viewmodels/ordersviewmodel.h new file mode 100644 index 0000000..0d666ba --- /dev/null +++ b/viewmodels/ordersviewmodel.h @@ -0,0 +1,43 @@ +#ifndef ORDERSVIEWMODEL_H +#define ORDERSVIEWMODEL_H + +#include + +#include "models/ordersmodel.h" +#include "models/usersmodel.h" + +class OrdersViewModel : public QAbstractListModel +{ + Q_OBJECT + +public: + explicit OrdersViewModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + QHash roleNames() const override; + +signals: + void ordersChanged(); + +private: + enum Roles { + UserName = Qt::UserRole + 1, + StoreName, + Date, + Time, + Amount, + Products + }; + + void reload(); + + OrdersModel m_ordersModel; + UsersModel m_usersModel; + BaseModel m_productsModel = BaseModel("products", this); + BaseModel m_storesModel = BaseModel("stores", this); + + QVariantList m_model; +}; + +#endif // ORDERSVIEWMODEL_H diff --git a/viewmodels/productsviewmodel.cpp b/viewmodels/productsviewmodel.cpp new file mode 100644 index 0000000..f1c6336 --- /dev/null +++ b/viewmodels/productsviewmodel.cpp @@ -0,0 +1,78 @@ +#include "productsviewmodel.h" + +#include "services/settingsservice.h" +#include "models/ordersmodel.h" + +ProductsViewModel::ProductsViewModel(QObject *parent) + : QObject{parent} +{ + connect(&m_productsModel, &BaseModel::dataChanged, this, &ProductsViewModel::productsChanged); +} + +QVariantList ProductsViewModel::products() const +{ + return m_productsModel.items(); +} + +QVariantList ProductsViewModel::order() const +{ + QVariantList res; + + for (auto it = m_order.constBegin(); it != m_order.constEnd(); ++it) { + QVariantMap product = m_productsModel.item(it.key()); + product["count"] = it.value().toMap().value("quantity").toDouble(); + res << product; + } + + return res; +} + +float ProductsViewModel::orderSum() const +{ + float res = 0.0; + + for (auto it = m_order.constBegin(); it != m_order.constEnd(); ++it) { + QVariantMap product = m_productsModel.item(it.key()); + float price = product.value("price", 0.0).toFloat(); + float quantity = it.value().toMap().value("quantity").toDouble(); + res += quantity * price; + } + + return res; +} + +void ProductsViewModel::setOrderValue(const QString &productId, float value) +{ + if (value) { + float price = m_productsModel.itemProperty(productId, "price", 0.0).toFloat(); + m_order[productId] = QVariantMap { + { "productId", productId }, + { "quantity", value}, + { "price", price } + }; + } else { + m_order.remove(productId); + } + + emit orderChanged(); +} + +void ProductsViewModel::submitOrder() +{ + OrdersModel model; + model.submitOrder(QVariantMap { + { "userId", settings()->selectedUserId() }, + { "storeId", settings()->selectedStoreId() }, + { "products", m_order.values() }, + { "amount", orderSum() } + }); + + m_order.clear(); + + emit orderChanged(); +} + +SettingsService *ProductsViewModel::settings() const +{ + return SettingsService::instance(); +} diff --git a/viewmodels/productsviewmodel.h b/viewmodels/productsviewmodel.h new file mode 100644 index 0000000..4e92fbf --- /dev/null +++ b/viewmodels/productsviewmodel.h @@ -0,0 +1,38 @@ +#ifndef PRODUCTSVIEWMODEL_H +#define PRODUCTSVIEWMODEL_H + +#include + +#include "models/basemodel.h" + +class SettingsService; +class ProductsViewModel : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QVariantList products READ products NOTIFY productsChanged) + Q_PROPERTY(QVariantList order READ order NOTIFY orderChanged) + Q_PROPERTY(float orderSum READ orderSum NOTIFY orderChanged) + +public: + explicit ProductsViewModel(QObject *parent = nullptr); + + QVariantList products() const; + QVariantList order() const; + float orderSum() const; + + Q_INVOKABLE void setOrderValue(const QString &productId, float value); + Q_INVOKABLE void submitOrder(); + +signals: + void productsChanged(); + void orderChanged(); + +private: + SettingsService *settings() const; + + BaseModel m_productsModel = BaseModel("products", this); + QVariantMap m_order; +}; + +#endif // PRODUCTSVIEWMODEL_H diff --git a/viewmodels/settingsviewmodel.cpp b/viewmodels/settingsviewmodel.cpp new file mode 100644 index 0000000..443f6e4 --- /dev/null +++ b/viewmodels/settingsviewmodel.cpp @@ -0,0 +1,57 @@ +#include "settingsviewmodel.h" + +SettingsViewModel::SettingsViewModel(QObject *parent) + : QAbstractListModel{parent} +{ + m_items << SettingItem { tr("BeerLog service address"), "serverAddress", "text" } + << SettingItem { tr("Selected user id"), "selectedUserId", "choice", "users" } + << SettingItem { tr("Selected store"), "selectedStoreId", "choice", "stores" }; + + m_models["users"] = new BaseModel("users", this); + m_models["stores"] = new BaseModel("stores", this); +} + + +int SettingsViewModel::rowCount(const QModelIndex &) const +{ + return m_items.count(); +} + +QVariant SettingsViewModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + return {}; + } + + SettingItem item = m_items.at(index.row()); + + switch (role) { + case Roles::Title: return item.title; + case Roles::PropertyName: return item.propertyName; + case Roles::ControlType: return item.controlType; + case Roles::Model: return model(item.modelName); + default: + break; + } + + return {}; +} + +QHash SettingsViewModel::roleNames() const +{ + return QHash { + { Roles::Title, "title" }, + { Roles::PropertyName, "name" }, + { Roles::ControlType, "control" }, + { Roles::Model, "choiceModel" } + }; +} + +QVariant SettingsViewModel::model(const QString &modelName) const +{ + if (!m_models.contains(modelName)) { + return {}; + } + + return m_models[modelName]->items(); +} diff --git a/viewmodels/settingsviewmodel.h b/viewmodels/settingsviewmodel.h new file mode 100644 index 0000000..c35cf24 --- /dev/null +++ b/viewmodels/settingsviewmodel.h @@ -0,0 +1,47 @@ +#ifndef SETTINGSVIEWMODEL_H +#define SETTINGSVIEWMODEL_H + +#include + +#include "models/basemodel.h" + +class SettingsViewModel : public QAbstractListModel +{ + Q_OBJECT + +public: + explicit SettingsViewModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + QHash roleNames() const override; + +private: + enum Roles { + Title = Qt::UserRole + 1, + PropertyName, + ControlType, + Model + }; + + struct SettingItem + { + QString title; + QString propertyName; + QString controlType; + QString modelName; + + SettingItem(const QString &title, const QString &propertyName, const QString &controlType, const QString &modelName = {}) : + title(title), + propertyName(propertyName), + controlType(controlType), + modelName(modelName) {} + }; + + QVariant model(const QString &modelName) const; + + QList m_items; + QMap m_models; +}; + +#endif // SETTINGSVIEWMODEL_H diff --git a/viewmodels/storesviewmodel.cpp b/viewmodels/storesviewmodel.cpp new file mode 100644 index 0000000..6d1b23c --- /dev/null +++ b/viewmodels/storesviewmodel.cpp @@ -0,0 +1,38 @@ +#include "storesviewmodel.h" + +#include "services/settingsservice.h" + +StoresViewModel::StoresViewModel(QObject *parent) + : QObject{parent} +{ + connect(&m_storesModel, &BaseModel::dataChanged, this, &StoresViewModel::storesChanged); + connect(&m_storesModel, &BaseModel::dataChanged, this, &StoresViewModel::selectedStoreNameChanged); + + connect(settings(), &SettingsService::selectedStoreIdChanged, this, &StoresViewModel::selectedStoreChanged); + connect(settings(), &SettingsService::selectedStoreIdChanged, this, &StoresViewModel::selectedStoreNameChanged); +} + +QVariantList StoresViewModel::stores() const +{ + return m_storesModel.items(); +} + +QString StoresViewModel::selectedStore() const +{ + return settings()->selectedStoreId(); +} + +void StoresViewModel::setSelectedStore(const QString &newSelectedStore) +{ + settings()->setSelectedStoreId(newSelectedStore); +} + +QString StoresViewModel::selectedStoreName() const +{ + return m_storesModel.itemProperty(selectedStore(), "name").toString(); +} + +SettingsService *StoresViewModel::settings() const +{ + return SettingsService::instance(); +} diff --git a/viewmodels/storesviewmodel.h b/viewmodels/storesviewmodel.h new file mode 100644 index 0000000..ea77794 --- /dev/null +++ b/viewmodels/storesviewmodel.h @@ -0,0 +1,36 @@ +#ifndef STORESVIEWMODEL_H +#define STORESVIEWMODEL_H + +#include + +#include "models/basemodel.h" + +class SettingsService; +class StoresViewModel : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QVariantList stores READ stores NOTIFY storesChanged) + Q_PROPERTY(QString selectedStore READ selectedStore WRITE setSelectedStore NOTIFY selectedStoreChanged) + Q_PROPERTY(QString selectedStoreName READ selectedStoreName NOTIFY selectedStoreNameChanged) + +public: + explicit StoresViewModel(QObject *parent = nullptr); + + QVariantList stores() const; + QString selectedStore() const; + void setSelectedStore(const QString &newSelectedStore); + QString selectedStoreName() const; + +signals: + void storesChanged(); + void selectedStoreChanged(); + void selectedStoreNameChanged(); + +private: + SettingsService *settings() const; + + BaseModel m_storesModel = BaseModel("stores", this); +}; + +#endif // STORESVIEWMODEL_H diff --git a/viewmodels/usersviewmodel.cpp b/viewmodels/usersviewmodel.cpp index 4eabb6a..e26c528 100644 --- a/viewmodels/usersviewmodel.cpp +++ b/viewmodels/usersviewmodel.cpp @@ -5,8 +5,8 @@ UsersViewModel::UsersViewModel(QObject *parent) : QObject{parent} { - connect(&m_usersModel, &AbstractModel::dataChanged, this, &UsersViewModel::usersChanged); - connect(&m_usersModel, &AbstractModel::dataChanged, this, &UsersViewModel::selectedUserNameChanged); + connect(&m_usersModel, &BaseModel::dataChanged, this, &UsersViewModel::usersChanged); + connect(&m_usersModel, &BaseModel::dataChanged, this, &UsersViewModel::selectedUserNameChanged); connect(settings(), &SettingsService::selectedUserIdChanged, this, &UsersViewModel::selectedUserChanged); connect(settings(), &SettingsService::selectedUserIdChanged, this, &UsersViewModel::selectedUserNameChanged); @@ -24,16 +24,12 @@ QString UsersViewModel::selectedUser() const void UsersViewModel::setSelectedUser(const QString &newSelectedUser) { - if (selectedUser() == newSelectedUser) { - return; - } - settings()->setSelectedUserId(newSelectedUser); } QString UsersViewModel::selectedUserName() const { - return m_usersModel.userName(selectedUser()); + return m_usersModel.itemProperty(selectedUser(), "name").toString(); } SettingsService *UsersViewModel::settings() const