diff options
Diffstat (limited to 'core/src/UI/UI_DatabaseView.cpp')
-rw-r--r-- | core/src/UI/UI_DatabaseView.cpp | 668 |
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(); - } -} |