aboutsummaryrefslogtreecommitdiff
path: root/core/src/UI/UI_DatabaseView.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/UI/UI_DatabaseView.cpp')
-rw-r--r--core/src/UI/UI_DatabaseView.cpp668
1 files changed, 0 insertions, 668 deletions
diff --git a/core/src/UI/UI_DatabaseView.cpp b/core/src/UI/UI_DatabaseView.cpp
deleted file mode 100644
index e128a59..0000000
--- a/core/src/UI/UI_DatabaseView.cpp
+++ /dev/null
@@ -1,668 +0,0 @@
-#include "UI.hpp"
-
-#include "Model/Filter.hpp"
-#include "Model/GlobalStates.hpp"
-#include "Model/Project.hpp"
-#include "Utils/I18n.hpp"
-#include "Utils/ScopeGuard.hpp"
-#include "Utils/Time.hpp"
-
-#include <IconsFontAwesome.h>
-#include <SQLiteCpp/Statement.h>
-#include <imgui.h>
-#include <tsl/robin_map.h>
-#include <cstdint>
-#include <iostream>
-#include <memory>
-#include <vector>
-
-namespace CPLT_UNITY_ID {
-
-// TODO move to Settings
-constexpr int kMaxEntriesPerPage = 32;
-constexpr int kSummaryItemCount = 3;
-constexpr int kSummaryMaxLength = 25;
-
-std::pair<int, int> SplitEntryIndex(int entryIdx)
-{
- int page = entryIdx / kMaxEntriesPerPage;
- int row = entryIdx % kMaxEntriesPerPage;
- return { page, row };
-}
-
-enum class DeliveryDirection
-{
- FactoryToWarehouse,
- WarehouseToCustomer,
-};
-
-struct Item
-{
- int ItemId;
- int Count;
-};
-
-struct DeliveryEntry
-{
- std::vector<Item> Items;
- std::string ItemsSummary;
- std::string ShipmentTime;
- std::string ArriveTime;
- DeliveryDirection Direction;
-
- const char* StringifyDirection() const
- {
- switch (Direction) {
- case DeliveryDirection::FactoryToWarehouse: return "Factory to warehouse";
- case DeliveryDirection::WarehouseToCustomer: return "Warehouse to customer";
- }
- }
-};
-
-struct SaleEntry
-{
- static constexpr auto kType = DeliveryDirection::WarehouseToCustomer;
-
- std::vector<DeliveryEntry> AssociatedDeliveries;
- std::vector<Item> Items;
- std::string ItemsSummary;
- std::string Customer;
- std::string Deadline;
- std::string DeliveryTime;
- int Id;
- bool DeliveriesCached = false;
-};
-
-struct PurchaseEntry
-{
- static constexpr auto kType = DeliveryDirection::FactoryToWarehouse;
-
- std::vector<DeliveryEntry> AssociatedDeliveries;
- std::vector<Item> Items;
- std::string ItemsSummary;
- std::string Factory;
- std::string OrderTime;
- std::string DeliveryTime;
- int Id;
- bool DeliveriesCached;
-};
-
-template <class T>
-class GenericTableView
-{
-public:
- // clang-format off
- static constexpr bool kHasItems = requires(T t)
- {
- t.Items;
- t.ItemsSummary;
- };
- static constexpr bool kHasCustomer = requires(T t) { t.Customer; };
- static constexpr bool kHasDeadline = requires(T t) { t.Deadline; };
- static constexpr bool kHasFactory = requires(T t) { t.Factory; };
- static constexpr bool kHasOrderTime = requires(T t) { t.OrderTime; };
- static constexpr bool kHasCompletionTime = requires(T t) { t.DeliveryTime; };
- static constexpr int kColumnCount = kHasItems + kHasCustomer + kHasDeadline + kHasFactory + kHasOrderTime + kHasCompletionTime;
- // clang-format on
-
- using Page = std::vector<T>;
-
- struct QueryStatements
- {
- SQLite::Statement* GetRowCount;
- SQLite::Statement* GetRows;
- SQLite::Statement* GetItems;
- SQLite::Statement* FilterRows;
- } Statements;
-
-protected:
- // Translation entries for implementer to fill out
- const char* mEditDialogTitle;
-
- Project* mProject;
- Page* mCurrentPage = nullptr;
-
- /// Current active filter object, or \c nullptr.
- std::unique_ptr<TableRowsFilter> mActiveFilter;
-
- tsl::robin_map<int, Page> mPages;
-
- /// A vector of entry indices (in \c mEntries) that are visible under the current filter.
- std::vector<int> mActiveEntries;
-
- /// Number of rows in the table.
- int mRowCount;
- /// Last possible page for the current set table and filter (inclusive).
- int mLastPage;
-
- /// The current page the user is on.
- int mCurrentPageNumber;
-
- /// Row index of the select entry
- int mSelectRow;
-
-public:
- /// Calculate the first visible row's entry index.
- int GetFirstVisibleRowIdx() const
- {
- return mCurrentPageNumber * kMaxEntriesPerPage;
- }
-
- Project* GetProject() const
- {
- return mProject;
- }
-
- void OnProjectChanged(Project* newProject)
- {
- mProject = newProject;
-
- auto& stmt = *Statements.GetRowCount;
- if (stmt.executeStep()) {
- mRowCount = stmt.getColumn(0).getInt();
- } else {
- std::cerr << "Failed to fetch row count from SQLite.\n";
- mRowCount = 0;
- }
-
- mActiveFilter = nullptr;
- mActiveEntries.clear();
-
- mPages.clear();
- mCurrentPage = nullptr;
- UpdateLastPage();
- SetPage(0);
-
- mSelectRow = -1;
- }
-
- TableRowsFilter* GetFilter() const
- {
- return mActiveFilter.get();
- }
-
- void OnFilterChanged()
- {
- auto& stmt = *Statements.FilterRows;
- // clang-format off
- DEFER { stmt.reset(); };
- // clang-format on
-
- // TODO lazy loading when too many results
- mActiveEntries.clear();
- int columnIdx = stmt.getColumnIndex("Id");
- while (stmt.executeStep()) {
- mActiveEntries.push_back(stmt.getColumn(columnIdx).getInt());
- }
-
- UpdateLastPage();
- SetPage(0);
-
- mSelectRow = -1;
- }
-
- void OnFilterChanged(std::unique_ptr<TableRowsFilter> filter)
- {
- mActiveFilter = std::move(filter);
- OnFilterChanged();
- }
-
- void Display()
- {
- bool dummy = true;
-
- if (ImGui::Button(ICON_FA_ARROW_LEFT, mCurrentPageNumber == 0)) {
- SetPage(mCurrentPageNumber - 1);
- }
-
- ImGui::SameLine();
- // +1 to convert from 0-based indices to 1-based, for human legibility
- ImGui::Text("%d/%d", mCurrentPageNumber + 1, mLastPage + 1);
-
- ImGui::SameLine();
- if (ImGui::Button(ICON_FA_ARROW_RIGHT, mCurrentPageNumber == mLastPage)) {
- SetPage(mCurrentPageNumber + 1);
- }
-
- ImGui::SameLine();
- if (ImGui::Button(ICON_FA_PLUS " " I18N_TEXT("Add", L10N_ADD))) {
- // TODO
- }
-
- ImGui::SameLine();
- if (ImGui::Button(ICON_FA_EDIT " " I18N_TEXT("Edit", L10N_EDIT), mSelectRow == -1)) {
- ImGui::OpenPopup(mEditDialogTitle);
- }
- if (ImGui::BeginPopupModal(mEditDialogTitle, &dummy, ImGuiWindowFlags_AlwaysAutoResize)) {
- auto& entry = (*mCurrentPage)[mSelectRow];
- int entryIdx = GetFirstVisibleRowIdx() + mSelectRow;
- EditEntry(entry, entryIdx, mSelectRow);
- ImGui::EndPopup();
- }
-
- ImGui::SameLine();
- if (ImGui::Button(ICON_FA_TRASH " " I18N_TEXT("Delete", L10N_DELETE), mSelectRow == -1)) {
- // TODO
- }
-
- ImGui::Columns(2);
- {
- DisplayMainTable();
- ImGui::NextColumn();
-
- if (mSelectRow == -1) {
- ImGui::TextWrapped("%s", I18N_TEXT("Select an entry to show associated deliveries", L10N_DATABASE_MESSAGE_NO_ORDER_SELECTED));
- } else {
- DisplayDeliveriesTable();
- }
- ImGui::NextColumn();
- }
- ImGui::Columns(1);
- }
-
- void SetPage(int page)
- {
- mCurrentPageNumber = page;
- mCurrentPage = &LoadAndGetPage(page);
- mSelectRow = -1;
- }
-
-private:
- static int CalcPageForRowId(int64_t entryIdx)
- {
- return entryIdx / kMaxEntriesPerPage;
- }
-
- /// Calculate range [begin, end) of index for the list of entries that are currently visible that the path-th page would show.
- /// i.e. when there is a filter, look into \c mActiveEntryIndices; when there is no filter, use directly.
- static std::pair<int64_t, int64_t> CalcRangeForPage(int page)
- {
- int begin = page * kMaxEntriesPerPage;
- return { begin, begin + kMaxEntriesPerPage };
- }
-
- void DisplayMainTable()
- {
- if (ImGui::BeginTable("DataTable", kColumnCount, ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollX)) {
-
- if constexpr (kHasCustomer) ImGui::TableSetupColumn(I18N_TEXT("Customer", L10N_DATABASE_COLUMN_CUSTOMER));
- if constexpr (kHasDeadline) ImGui::TableSetupColumn(I18N_TEXT("Deadline", L10N_DATABASE_COLUMN_DEADLINE));
- if constexpr (kHasFactory) ImGui::TableSetupColumn(I18N_TEXT("Factory", L10N_DATABASE_COLUMN_FACTORY));
- if constexpr (kHasOrderTime) ImGui::TableSetupColumn(I18N_TEXT("Order time", L10N_DATABASE_COLUMN_ORDER_TIME));
- if constexpr (kHasCompletionTime) ImGui::TableSetupColumn(I18N_TEXT("Completion time", L10N_DATABASE_COLUMN_DELIVERY_TIME));
- if constexpr (kHasItems) ImGui::TableSetupColumn(I18N_TEXT("Items", L10N_DATABASE_COLUMN_ITEMS));
- ImGui::TableHeadersRow();
-
- if (mActiveFilter) {
- // TODO
- auto [begin, end] = CalcRangeForPage(mCurrentPageNumber);
- end = std::min(end, (int64_t)mActiveEntries.size() - 1);
- for (int i = begin; i < end; ++i) {
- int rowIdx = mActiveEntries[i];
- DisplayEntry(rowIdx);
- }
- } else {
- int firstRowIdx = mCurrentPageNumber * kMaxEntriesPerPage;
- auto& page = *mCurrentPage;
-
- int end = std::min((int)page.size(), kMaxEntriesPerPage);
- for (int i = 0; i < end; ++i) {
- DisplayEntry(page[i], i, firstRowIdx + i);
- }
- }
-
- ImGui::EndTable();
- }
- }
-
- void DisplayEntry(int rowIdx)
- {
- // TODO
- // auto [pageNumber, pageEntry] = SplitRowIndex(rowIdx);
- // auto& entry = LoadAndGetPage(pageNumber)[pageEntry];
- // DisplayEntry(entry, rowIdx);
- }
-
- void DisplayEntry(T& entry, int rowIdx, int entryIdx)
- {
- ImGui::PushID(rowIdx);
- ImGui::TableNextRow();
-
- if constexpr (kHasCustomer) {
- ImGui::TableNextColumn();
- if (ImGui::Selectable(entry.Customer.c_str(), mSelectRow == rowIdx, ImGuiSelectableFlags_SpanAllColumns)) {
- mSelectRow = rowIdx;
- }
- }
-
- if constexpr (kHasDeadline) {
- ImGui::TableNextColumn();
- ImGui::TextUnformatted(entry.Deadline.c_str());
- }
-
- if constexpr (kHasFactory) {
- ImGui::TableNextColumn();
- if (ImGui::Selectable(entry.Factory.c_str(), mSelectRow == rowIdx, ImGuiSelectableFlags_SpanAllColumns)) {
- mSelectRow = rowIdx;
- }
- }
-
- if constexpr (kHasOrderTime) {
- ImGui::TableNextColumn();
- ImGui::TextUnformatted(entry.OrderTime.c_str());
- }
-
- if constexpr (kHasCompletionTime) {
- ImGui::TableNextColumn();
- if (entry.DeliveryTime.empty()) {
- ImGui::TextUnformatted(I18N_TEXT("Not delivered", L10N_DATABASE_MESSAGE_NOT_DELIVERED));
- } else {
- ImGui::TextUnformatted(entry.DeliveryTime.c_str());
- }
- }
-
- if constexpr (kHasItems) {
- ImGui::TableNextColumn();
- if (ImGui::TreeNode(entry.ItemsSummary.c_str())) {
- DrawItems(entry.Items);
- ImGui::TreePop();
- }
- }
-
- ImGui::PopID();
- }
-
- void EditEntry(T& entry, int rowIdx, int entryIdx)
- {
- // TODO
- }
-
- void DisplayDeliveriesTable()
- {
- if (ImGui::BeginTable("DeliveriesTable", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollX)) {
-
- ImGui::TableSetupColumn(I18N_TEXT("Shipment time", L10N_DATABASE_COLUMN_SHIPMENT_TIME));
- ImGui::TableSetupColumn(I18N_TEXT("Arrival time", L10N_DATABASE_COLUMN_ARRIVAL_TIME));
- ImGui::TableSetupColumn(I18N_TEXT("Items", L10N_DATABASE_COLUMN_ITEMS));
- ImGui::TableHeadersRow();
-
- auto& entry = (*mCurrentPage)[mSelectRow];
- auto& deliveries = entry.AssociatedDeliveries;
- for (auto& delivery : deliveries) {
- ImGui::TableNextRow();
-
- ImGui::TableNextColumn();
- ImGui::TextUnformatted(delivery.ShipmentTime.c_str());
-
- ImGui::TableNextColumn();
- ImGui::TextUnformatted(delivery.ArriveTime.c_str());
-
- ImGui::TableNextColumn();
- if (ImGui::TreeNode(delivery.ItemsSummary.c_str())) {
- DrawItems(delivery.Items);
- ImGui::TreePop();
- }
- }
-
- ImGui::EndTable();
- }
- }
-
- std::vector<Item> LoadItems(SQLite::Statement& stmt, int64_t id)
- {
- // clang-format off
- DEFER { stmt.reset(); };
- // clang-format on
-
- stmt.bind(1, id);
-
- std::vector<Item> entries;
- int itemIdCol = stmt.getColumnIndex("ItemId");
- int countCol = stmt.getColumnIndex("Count");
- while (stmt.executeStep()) {
- entries.push_back(Item{
- .ItemId = stmt.getColumn(itemIdCol).getInt(),
- .Count = stmt.getColumn(countCol).getInt(),
- });
- }
-
- return entries;
- }
-
- std::string CreateItemsSummary(const std::vector<Item>& items)
- {
- if (items.empty()) {
- return "<empty>";
- }
-
- std::string result;
- for (int i = 0, max = std::min((int)items.size(), kSummaryItemCount); i < max; ++i) {
- auto& name = mProject->Products.Find(items[i].ItemId)->GetName();
- if (result.length() + name.length() > kSummaryMaxLength) {
- break;
- }
-
- result += name;
- result += ", ";
- }
-
- // Remove ", "
- result.pop_back();
- result.pop_back();
-
- result += "...";
-
- return result;
- }
-
- std::vector<DeliveryEntry> LoadDeliveriesEntries(int64_t orderId, DeliveryDirection type)
- {
- bool outgoingFlag;
- switch (type) {
- case DeliveryDirection::FactoryToWarehouse: outgoingFlag = false; break;
- case DeliveryDirection::WarehouseToCustomer: outgoingFlag = true; break;
- }
-
- auto& stmt = mProject->Database.GetDeliveries().FilterByTypeAndId;
- // clang-format off
- DEFER { stmt.reset(); };
- // clang-format on
-
- stmt.bind(1, orderId);
- stmt.bind(2, outgoingFlag);
-
- std::vector<DeliveryEntry> entries;
- int rowIdCol = stmt.getColumnIndex("Id");
- int sendTimeCol = stmt.getColumnIndex("ShipmentTime");
- int arrivalTimeCol = stmt.getColumnIndex("ArrivalTime");
- while (stmt.executeStep()) {
- auto items = LoadItems(
- mProject->Database.GetDeliveries().GetItems,
- stmt.getColumn(rowIdCol).getInt64());
- auto summary = CreateItemsSummary(items);
-
- entries.push_back(DeliveryEntry{
- .Items = std::move(items),
- .ItemsSummary = std::move(summary),
- .ShipmentTime = TimeUtils::StringifyTimeStamp(stmt.getColumn(arrivalTimeCol).getInt64()),
- .ArriveTime = TimeUtils::StringifyTimeStamp(stmt.getColumn(sendTimeCol).getInt64()),
- .Direction = type,
- });
- }
-
- return entries;
- }
-
- Page& LoadAndGetPage(int page)
- {
- auto iter = mPages.find(page);
- if (iter != mPages.end()) {
- return iter.value();
- }
-
- auto& stmt = *Statements.GetRows;
- // clang-format off
- DEFER { stmt.reset(); };
- // clang-format on
-
- stmt.bind(1, kMaxEntriesPerPage);
- stmt.bind(2, page * kMaxEntriesPerPage);
-
- // If a field flag is false, the column index won't be used (controlled by other if constexpr's downstream)
- // so there is no UB here
-
- // This column is always necessary (and present) because the deliveries table require it
- int idCol = stmt.getColumnIndex("Id");
-
- int customerCol;
- if constexpr (kHasCustomer) customerCol = stmt.getColumnIndex("Customer");
-
- int deadlineCol;
- if constexpr (kHasDeadline) deadlineCol = stmt.getColumnIndex("Deadline");
-
- int factoryCol;
- if constexpr (kHasFactory) factoryCol = stmt.getColumnIndex("Factory");
-
- int orderTimeCol;
- if constexpr (kHasOrderTime) orderTimeCol = stmt.getColumnIndex("OrderTime");
-
- int deliveryTimeCol;
- if constexpr (kHasCompletionTime) deliveryTimeCol = stmt.getColumnIndex("DeliveryTime");
-
- Page entries;
- while (stmt.executeStep()) {
- auto& entry = entries.emplace_back();
-
- auto id = stmt.getColumn(idCol).getInt64();
- entry.AssociatedDeliveries = LoadDeliveriesEntries(id, T::kType);
-
- if constexpr (kHasItems) {
- auto items = LoadItems(
- *Statements.GetItems,
- id);
- auto itemsSummary = CreateItemsSummary(items);
- entry.Items = std::move(items);
- entry.ItemsSummary = std::move(itemsSummary);
- }
-
- if constexpr (kHasCustomer) {
- auto customerId = stmt.getColumn(customerCol).getInt();
- entry.Customer = mProject->Customers.Find(customerId)->GetName();
- }
-
- if constexpr (kHasDeadline) {
- auto timeStamp = stmt.getColumn(deadlineCol).getInt64();
- entry.Deadline = TimeUtils::StringifyTimeStamp(timeStamp);
- }
-
- if constexpr (kHasFactory) {
- auto factoryId = stmt.getColumn(factoryCol).getInt();
- entry.Factory = mProject->Factories.Find(factoryId)->GetName();
- }
-
- if constexpr (kHasOrderTime) {
- auto timeStamp = stmt.getColumn(orderTimeCol).getInt64();
- entry.OrderTime = TimeUtils::StringifyTimeStamp(timeStamp);
- }
-
- if constexpr (kHasCompletionTime) {
- auto timeStamp = stmt.getColumn(deliveryTimeCol).getInt64();
- entry.DeliveryTime = TimeUtils::StringifyTimeStamp(timeStamp);
- }
- }
-
- auto [res, _] = mPages.try_emplace(page, std::move(entries));
- return res.value();
- }
-
- void DrawItems(const std::vector<Item>& items)
- {
- for (auto& item : items) {
- auto& name = mProject->Products.Find(item.ItemId)->GetName();
- ImGui::Text("%s × %d", name.c_str(), item.Count);
- }
- }
-
- void UpdateLastPage()
- {
- mLastPage = mActiveEntries.empty()
- ? CalcPageForRowId(mRowCount)
- : CalcPageForRowId(mActiveEntries.back());
- }
-};
-
-class SalesTableView : public GenericTableView<SaleEntry>
-{
-public:
- SalesTableView()
- {
- mEditDialogTitle = I18N_TEXT("Edit sales entry", L10N_DATABASE_SALES_VIEW_EDIT_DIALOG_TITLE);
- }
-
-#pragma clang diagnostic push
-#pragma ide diagnostic ignored "HidingNonVirtualFunction"
- void OnProjectChanged(Project* newProject)
- {
- auto& table = newProject->Database.GetSales();
- Statements.GetRowCount = &table.GetRowCount;
- Statements.GetRows = &table.GetRows;
- Statements.GetItems = &table.GetItems;
- // TODO
- // stmts.FilterRowsStatement = ;
-
- GenericTableView<SaleEntry>::OnProjectChanged(newProject);
- }
-#pragma clang diagnostic pop
-};
-
-class PurchasesTableView : public GenericTableView<PurchaseEntry>
-{
-public:
- PurchasesTableView()
- {
- mEditDialogTitle = I18N_TEXT("Edit purchase entry", L10N_DATABASE_PURCHASES_VIEW_EDIT_DIALOG_TITLE);
- }
-
-#pragma clang diagnostic push
-#pragma ide diagnostic ignored "HidingNonVirtualFunction"
- void OnProjectChanged(Project* newProject)
- {
- auto& table = newProject->Database.GetPurchases();
- Statements.GetRowCount = &table.GetRowCount;
- Statements.GetRows = &table.GetRows;
- Statements.GetItems = &table.GetItems;
- // TODO
- // stmts.FilterRowsStatement = ;
-
- GenericTableView<PurchaseEntry>::OnProjectChanged(newProject);
- }
-#pragma clang diagnostic pop
-};
-} // namespace CPLT_UNITY_ID
-
-void UI::DatabaseViewTab()
-{
- auto& gs = GlobalStates::GetInstance();
-
- static Project* currentProject = nullptr;
- static CPLT_UNITY_ID::SalesTableView sales;
- static CPLT_UNITY_ID::PurchasesTableView purchases;
-
- if (currentProject != gs.GetCurrentProject()) {
- currentProject = gs.GetCurrentProject();
- sales.OnProjectChanged(currentProject);
- purchases.OnProjectChanged(currentProject);
- }
-
- if (ImGui::BeginTabBar("DatabaseViewTabs")) {
- if (ImGui::BeginTabItem(I18N_TEXT("Sales", L10N_DATABASE_SALES_VIEW_TAB_NAME))) {
- sales.Display();
- ImGui::EndTabItem();
- }
- if (ImGui::BeginTabItem(I18N_TEXT("Purchases", L10N_DATABASE_PURCHASES_VIEW_TAB_NAME))) {
- purchases.Display();
- ImGui::EndTabItem();
- }
- ImGui::EndTabBar();
- }
-}