/* * Copyright (c) 2025 Aldrik Ramaekers * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "ui.hpp" #include "strops.hpp" #include "memops.hpp" #include "locales.hpp" #include "importer.hpp" #include "countries.hpp" #include "administration.hpp" #include "administration_writer.hpp" extern void draw_contact_form(contact* buffer, bool viewing_only = false); static contact company_info; u32 tax_rate_count; tax_rate* tax_rates = 0; u32 cost_center_count; cost_center* cost_centers = 0; static int select_company_tab = 0; static ai_service new_service; void ui::destroy_settings() { memops::unalloc(tax_rates); memops::unalloc(cost_centers); } void ui::setup_settings() { select_company_tab = 1; company_info = administration::company_info_get(); if (administration::can_create_invoices()) { tax_rates = (tax_rate*)memops::alloc(sizeof(tax_rate) * 400); tax_rate_count = country::get_available_tax_rates(administration::company_info_get().address.country_code, tax_rates, 400); cost_center_count = administration::cost_center_count(); cost_centers = (cost_center*)memops::alloc(cost_center_count * sizeof(cost_center)); administration::cost_center_get_all(cost_centers); new_service = administration::get_ai_service(); } } static void draw_vat_rates() { tax_rate_type type_iter = tax_rate_type::TAX_RATE_OUTGOING_INVOICE; go_again: char id[MAX_LEN_SHORT_DESC]; strops::format(id, MAX_LEN_SHORT_DESC, "TableVatRates##%d", static_cast(type_iter)); if (ImGui::BeginTable(id, 4, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { ImGui::TableSetupColumn("##check", ImGuiTableColumnFlags_WidthFixed, 25); ImGui::TableSetupColumn(locale::get("settings.vat.table.outgoing"), ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn(locale::get("settings.vat.table.rates"), ImGuiTableColumnFlags_WidthFixed, 120); ImGui::TableSetupColumn(locale::get("settings.vat.table.section"), ImGuiTableColumnFlags_WidthFixed, 200); ImGui::TableHeadersRow(); for (u32 i = 0; i < tax_rate_count; i++) { tax_rate c = tax_rates[i]; if (c.type != type_iter) continue; ImGui::PushID(i); ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); bool toggle = administration::tax_rate_is_enabled(c) == A_ERR_SUCCESS; if (ImGui::Checkbox("##toggle", &toggle)) { if (toggle) administration::tax_rate_enable(c); else administration::tax_rate_disable(c); } char localized_code[MAX_LEN_LONG_DESC]; strops::format(localized_code, MAX_LEN_LONG_DESC, "taxrate.code.%s", c.internal_code); ImGui::TableSetColumnIndex(1); ImGui::TextUnformatted(locale::get(localized_code)); ImGui::TableSetColumnIndex(2); ImGui::Text("%.2f%%", c.rate); ImGui::TableSetColumnIndex(3); for (u32 x = 0; x < c.tax_section_count; x++) { if (x == 0) ImGui::TextUnformatted(c.tax_sections[x]); else ImGui::Text(", %s", c.tax_sections[x]); if (x != c.tax_section_count-1) ImGui::SameLine(0, 0); } ImGui::PopID(); } ImGui::EndTable(); ImGui::Spacing(); ImGui::Spacing(); ImGui::Spacing(); } type_iter = static_cast(static_cast(type_iter) + 1); if (type_iter < TAX_RATE_TYPE_END) goto go_again; } static void draw_cost_centers() { static bool is_adding_item = false; static cost_center new_cost_center; static bool is_editing_item = false; static u32 editing_item_index = 0; if (ImGui::BeginTable("TableCostCenters", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { ImGui::TableSetupColumn(locale::get("settings.costcenters.table.code"), ImGuiTableColumnFlags_WidthFixed, 140); ImGui::TableSetupColumn(locale::get("settings.costcenters.table.description")); ImGui::TableHeadersRow(); for (u32 i = 0; i < cost_center_count; i++) { cost_center c = cost_centers[i]; ImGui::TableNextRow(); // Column 1: Code of cost center. ImGui::TableSetColumnIndex(0); ImGui::Text(c.code); // Column 2: When editing, show inputs for new description. Else show stored description and check for modify request. ImGui::TableSetColumnIndex(1); if (is_editing_item && editing_item_index == i) { bool is_desc_valid = !(administration::cost_center_is_valid(new_cost_center) & A_ERR_MISSING_DESCRIPTION); if (!is_desc_valid) ImGui::PushStyleColor(ImGuiCol_FrameBg, IM_COL32(105, 43, 43, 255)); ImGui::InputText("##Description", new_cost_center.description, IM_ARRAYSIZE(new_cost_center.description)); if (!is_desc_valid) ImGui::PopStyleColor(); if (!is_desc_valid) ImGui::BeginDisabled(); ImGui::SameLine(); if (ImGui::Button(locale::get("form.save"))) { is_editing_item = false; is_adding_item = false; administration::cost_center_update(new_cost_center); memops::zero(&new_cost_center, sizeof(new_cost_center)); ui::destroy_settings(); ui::setup_settings(); } if (!is_desc_valid) ImGui::EndDisabled(); ImGui::SameLine(); if (ImGui::Button(locale::get("form.cancel"))) { is_editing_item = false; is_adding_item = false; memops::zero(&new_cost_center, sizeof(new_cost_center)); } } else { ImGui::Text(locale::get(c.description)); if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { is_editing_item = true; is_adding_item = false; editing_item_index = i; new_cost_center = c; } } } // When adding a new item. Show inputs for code and description, check validity, and handle save/cancel. // Form for new entry is displayed at bottom of list. if (is_adding_item) { ImGui::TableNextRow(); bool is_code_valid = !(administration::cost_center_is_valid(new_cost_center) & (A_ERR_CODE_EXISTS|A_ERR_MISSING_CODE)); if (!is_code_valid) ImGui::PushStyleColor(ImGuiCol_FrameBg, IM_COL32(105, 43, 43, 255)); ImGui::TableSetColumnIndex(0); ImGui::InputText("##Code", new_cost_center.code, IM_ARRAYSIZE(new_cost_center.code), ImGuiInputTextFlags_CharsUppercase|ImGuiInputTextFlags_CharsNoBlank); if (!is_code_valid) ImGui::PopStyleColor(); bool is_desc_valid = !(administration::cost_center_is_valid(new_cost_center) & A_ERR_MISSING_DESCRIPTION); if (!is_desc_valid) ImGui::PushStyleColor(ImGuiCol_FrameBg, IM_COL32(105, 43, 43, 255)); ImGui::TableSetColumnIndex(1); ImGui::InputText("##Description", new_cost_center.description, IM_ARRAYSIZE(new_cost_center.description)); if (!is_desc_valid) ImGui::PopStyleColor(); bool can_save = is_code_valid && is_desc_valid; if (!can_save) ImGui::BeginDisabled(); ImGui::SameLine(); if (ImGui::Button(locale::get("form.create"))) { is_adding_item = false; is_editing_item = false; administration::cost_center_add(new_cost_center); ui::destroy_settings(); ui::setup_settings(); } if (!can_save) ImGui::EndDisabled(); ImGui::SameLine(); if (ImGui::Button(locale::get("form.cancel"))) { is_adding_item = false; is_editing_item = false; memops::zero(&new_cost_center, sizeof(new_cost_center)); } } ImGui::EndTable(); } // If not adding a new item already, show create button at bottom of list. if (!is_adding_item && ImGui::Button(locale::get("form.create"))) { new_cost_center = administration::cost_center_create_empty(); is_adding_item = true; is_editing_item = false; } } static void draw_services() { static importer::model_list_request* model_request = 0; static bool set_model_on_load = false; // AI service if (ImGui::CollapsingHeader(locale::get("settings.services.ai_service"))) { char* ai_service_names[AI_PROVIDER_END]; for (u32 i = 0; i < AI_PROVIDER_END; i++) { ai_service_names[i] = importer::get_ai_provider_implementation((ai_provider)i).provider_name; } if (ImGui::BeginCombo(locale::get("settings.services.ai_service.provider"), ai_service_names[new_service.provider])) { for (u32 n = 0; n < AI_PROVIDER_END; n++) { bool is_selected = n == (uint32_t)new_service.provider; if (ImGui::Selectable(ai_service_names[n], is_selected)) { new_service.provider = (ai_provider)n; model_request = 0; set_model_on_load = true; } } ImGui::EndCombo(); } ImGui::InputTextWithHint(locale::get("settings.services.ai_service.pubkey"), locale::get("settings.services.ai_service.pubkey"), new_service.api_key_public, sizeof(new_service.api_key_public)); if (!model_request) { model_request = importer::ai_get_available_models(new_service.provider); } // Default to first result in model list, or hardcoded default model. if (model_request->status == importer::status::IMPORT_DONE) { if (set_model_on_load) { set_model_on_load = false; if (model_request->result_count > 0) { strops::copy(new_service.model_name, model_request->result[0], sizeof(new_service.model_name)); } else { strops::copy(new_service.model_name, importer::get_ai_provider_implementation(new_service.provider).default_model, sizeof(new_service.model_name)); } } } if (model_request->status == importer::status::IMPORT_DONE && model_request->error == I_ERR_SUCCESS) { if (ImGui::BeginCombo(locale::get("settings.services.ai_service.model"), new_service.model_name)) { for (u32 n = 0; n < model_request->result_count; n++) { bool is_selected = strops::equals(new_service.model_name, model_request->result[n]); if (ImGui::Selectable(model_request->result[n], is_selected)) { strops::copy(new_service.model_name, model_request->result[n], sizeof(new_service.model_name)); } } ImGui::EndCombo(); } } else { ImGui::BeginDisabled(); ImGui::BeginCombo(locale::get("settings.services.ai_service.model"), NULL, 1 << 20); { if (ImGui::BeginComboPreview()) { if (model_request->status != importer::status::IMPORT_DONE) { float radius = 10.0f; const ImVec4 col = ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered); const ImVec4 bg = ImGui::GetStyleColorVec4(ImGuiCol_Button); ImGui::LoadingIndicatorCircle("##loadingAnim", radius, bg, col, 6, 4.0f); ImGui::SameLine(); } ImGui::TextUnformatted(new_service.model_name); ImGui::EndComboPreview(); } //ImGui::EndCombo(); } ImGui::EndDisabled(); } if (ImGui::Button(locale::get("form.save"))) { administration::set_ai_service(new_service); } } } void draw_contact_form_ex(contact* buffer, bool viewing_only, bool with_autocomplete, bool active_countries_only); void ui::draw_settings() { if (ImGui::BeginTabBar("SettingsTabBar")) { if (ImGui::BeginTabItem(locale::get("settings.table.company"), nullptr, select_company_tab == 1 ? ImGuiTabItemFlags_SetSelected : 0)) { select_company_tab = 0; draw_contact_form_ex(&company_info, false, false, true); // Save button. bool can_save = administration::contact_is_valid(company_info) == A_ERR_SUCCESS; if (!can_save) ImGui::BeginDisabled(); ImGui::Spacing(); if (ImGui::Button(locale::get("form.save"))) { administration::company_info_set(company_info); } if (!can_save) ImGui::EndDisabled(); ImGui::EndTabItem(); } if (administration::can_create_invoices()) { if (ImGui::BeginTabItem(locale::get("settings.table.vatrates"))) { draw_vat_rates(); ImGui::EndTabItem(); } if (ImGui::BeginTabItem(locale::get("settings.table.costcenters"))) { draw_cost_centers(); ImGui::EndTabItem(); } if (ImGui::BeginTabItem(locale::get("settings.table.services"))) { draw_services(); ImGui::EndTabItem(); } } ImGui::EndTabBar(); } }