From 6abbea56a6bfa69cb5e576223301f0c992b646fa Mon Sep 17 00:00:00 2001 From: Aldrik Ramaekers Date: Sat, 8 Nov 2025 14:13:43 +0100 Subject: activity sidebar for invoices. --- include/administration.hpp | 227 ++++++++++++++++++++++-------------------- src/administration.cpp | 73 +++++++++++++- src/administration_reader.cpp | 2 + src/administration_writer.cpp | 6 +- src/locales/en.cpp | 7 +- src/ui/ui_expenses.cpp | 185 +++++++++++++++++++++++++--------- src/ui/ui_invoices.cpp | 74 +++++++++++--- 7 files changed, 396 insertions(+), 178 deletions(-) diff --git a/include/administration.hpp b/include/administration.hpp index 09e0cb2..c099318 100644 --- a/include/administration.hpp +++ b/include/administration.hpp @@ -44,6 +44,10 @@ #define MAX_LEN_PROJECT_REPORT_COSTCENTERS 50 #define MAX_BILLING_ITEMS 50 +#define ACTIVITY_MAX_PARAMS 3 +#define ACTIVITY_USER "user" +#define ACTIVITY_SYSTEM "system" + #define MY_COMPANY_ID "C/1" typedef enum @@ -396,6 +400,16 @@ typedef struct char api_key_public[MAX_LEN_API_KEY]; } ai_service; +typedef struct +{ + time_t timestamp; + char user_name[MAX_LEN_SHORT_DESC]; + char ref_id[MAX_LEN_ID]; + + char message[MAX_LEN_LONG_DESC]; + char params[ACTIVITY_MAX_PARAMS][MAX_LEN_LONG_DESC]; +} activity; + typedef struct { contact company_info; // Company info used for invoices. User cannot create invoices when this is empty/invalid. @@ -408,9 +422,10 @@ typedef struct list_t contacts; list_t projects; - list_t tax_rates; // Enabled tax rates. - list_t all_tax_rates; // Tax rates for all countries. + list_t tax_rates; // Enabled tax rates. + list_t all_tax_rates; // Tax rates for all countries. list_t cost_centers; + list_t activities; u32 invoice_count; u32 expense_count; @@ -449,152 +464,148 @@ namespace administration { // Setup functions. // ======================= - void create_empty(char* save_file); - void create_default(char* save_file); - void create_from_file(char* save_file); - void destroy(); + void create_empty(char* save_file); + void create_default(char* save_file); + void create_from_file(char* save_file); + void destroy(); + void sort_data(); // Callback functions. // ======================= - void set_administration_data_changed_event_callback(data_changed_event ev); - void set_data_deleted_event_callback(data_deleted_event ev); - void set_invoice_changed_event_callback(invoice_changed_event ev); - void set_contact_changed_event_callback(contact_changed_event ev); - void set_taxrate_changed_event_callback(taxrate_changed_event ev); - void set_costcenter_changed_event_callback(costcenter_changed_event ev); - void set_project_changed_event_callback(project_changed_event ev); + void set_administration_data_changed_event_callback(data_changed_event ev); + void set_data_deleted_event_callback(data_deleted_event ev); + void set_invoice_changed_event_callback(invoice_changed_event ev); + void set_contact_changed_event_callback(contact_changed_event ev); + void set_taxrate_changed_event_callback(taxrate_changed_event ev); + void set_costcenter_changed_event_callback(costcenter_changed_event ev); + void set_project_changed_event_callback(project_changed_event ev); // Company info functions. // ======================= - contact company_info_get(); - void company_info_import(contact data); - void company_info_set(contact data); + contact company_info_get(); + void company_info_import(contact data); + void company_info_set(contact data); // Tax functions. // ======================= - tax_line* get_tax_line_from_report(tax_report* quarter, char* category); - void create_tax_statement(tax_statement* statement); + tax_line* get_tax_line_from_report(tax_report* quarter, char* category); + void create_tax_statement(tax_statement* statement); // Other functions. // ======================= - char* get_file_path(); - s32 get_next_id(); - s32 get_next_sequence_number(); - char* get_currency_symbol_for_currency(char* code); - char* get_default_currency(); - time_t get_default_invoice_expire_duration(); - ai_service get_ai_service(); - - void set_file_path(char* path); - void set_next_id(s32 nr); - void set_next_sequence_number(s32 nr); - void set_ai_service(ai_service provider); - - void create_income_statement(income_statement* statement); - bool can_create_invoices(); + char* get_file_path(); + s32 get_next_id(); + s32 get_next_sequence_number(); + char* get_currency_symbol_for_currency(char* code); + char* get_default_currency(); + time_t get_default_invoice_expire_duration(); + ai_service get_ai_service(); + void set_file_path(char* path); + void set_next_id(s32 nr); + void set_next_sequence_number(s32 nr); + void set_ai_service(ai_service provider); + void create_income_statement(income_statement* statement); + bool can_create_invoices(); // Contact functions. // ======================= - u32 contact_count(contact_filter* filter = 0); - contact contact_create_empty(); - a_err contact_import(contact data); - a_err contact_add(contact data); - a_err contact_update(contact data); - a_err contact_remove(contact data); - - a_err addressee_is_valid(delivery_info data); - a_err contact_is_valid(contact data); - bool contact_equals(contact c1, contact c2); - - a_err contact_get_by_id(contact* buffer, char* id); - int contact_get_autocompletions(contact* buffer, int buf_size, char* name); - u32 contact_get_partial_list(u32 page_index, u32 page_size, contact* buffer, contact_filter* filter = 0); - u32 contact_get_all(contact* buffer); + u32 contact_count(contact_filter* filter = 0); + contact contact_create_empty(); + a_err contact_import(contact data); + a_err contact_add(contact data); + a_err contact_update(contact data); + a_err contact_remove(contact data); + a_err addressee_is_valid(delivery_info data); + a_err contact_is_valid(contact data); + bool contact_equals(contact c1, contact c2); + a_err contact_get_by_id(contact* buffer, char* id); + int contact_get_autocompletions(contact* buffer, int buf_size, char* name); + u32 contact_get_partial_list(u32 page_index, u32 page_size, contact* buffer, contact_filter* filter = 0); + u32 contact_get_all(contact* buffer); // Project functions. // ======================= - u32 project_count(); - project project_create_empty(); - a_err project_import(project data); - a_err project_add(project data); - a_err project_update(project data); - a_err project_remove(project data); - void project_cancel(project data); - - a_err project_is_valid(project data); - - char* project_get_status_string(project data); - u32 project_get_all(project* buffer); - u32 project_get_partial_list(u32 page_index, u32 page_size, project* buffer); - a_err project_get_by_id(project* buffer, char* id); + u32 project_count(); + project project_create_empty(); + a_err project_import(project data); + a_err project_add(project data); + a_err project_update(project data); + a_err project_remove(project data); + void project_cancel(project data); + a_err project_is_valid(project data); + char* project_get_status_string(project data); + u32 project_get_all(project* buffer); + u32 project_get_partial_list(u32 page_index, u32 page_size, project* buffer); + a_err project_get_by_id(project* buffer, char* id); // Tax rate functions. // ======================= - u32 tax_rate_count(); - tax_rate tax_rate_create_empty(); - a_err tax_rate_import(tax_rate data); - a_err tax_rate_enable(tax_rate data); - a_err tax_rate_is_enabled(tax_rate data); - a_err tax_rate_disable(tax_rate data); + u32 tax_rate_count(); + tax_rate tax_rate_create_empty(); + a_err tax_rate_import(tax_rate data); + a_err tax_rate_enable(tax_rate data); + a_err tax_rate_is_enabled(tax_rate data); + a_err tax_rate_disable(tax_rate data); /// @brief Find tax rate by internal code, across all available tax rates. - a_err tax_rate_get_by_internal_code(tax_rate* buffer, char* id); + a_err tax_rate_get_by_internal_code(tax_rate* buffer, char* id); /// @brief Get all enabled tax rates. /// @return tax rate count - u32 tax_rate_get_all(tax_rate* buffer, tax_rate_type type); + u32 tax_rate_get_all(tax_rate* buffer, tax_rate_type type); // Cost center functions. // ======================= - u32 cost_center_count(); + u32 cost_center_count(); cost_center cost_center_create_empty(); - a_err cost_center_import(cost_center data); - a_err cost_center_add(cost_center data); - a_err cost_center_update(cost_center data); - - a_err cost_center_is_valid(cost_center data); - - u32 cost_center_get_all(cost_center* buffer); - a_err cost_center_get_by_id(cost_center* buffer, char* id); + a_err cost_center_import(cost_center data); + a_err cost_center_add(cost_center data); + a_err cost_center_update(cost_center data); + a_err cost_center_is_valid(cost_center data); + u32 cost_center_get_all(cost_center* buffer); + a_err cost_center_get_by_id(cost_center* buffer, char* id); // Invoice functions. // ======================= - u32 invoice_count(); - u32 invoice_get_incomming_count(); - u32 invoice_get_outgoing_count(); - invoice invoice_create_empty(); - a_err invoice_import(invoice* invoice); - a_err invoice_add(invoice* invoice); - a_err invoice_update(invoice* invoice); - a_err invoice_remove(invoice* invoice); - void invoice_set_currency(invoice* invoice, char* currency); - invoice invoice_create_copy(invoice* invoice); - void invoice_destroy(invoice* invoice); - - a_err invoice_is_valid(invoice* invoice); - bool invoice_has_intra_community_services(invoice* invoice); - - char* invoice_get_status_string(invoice* invoice); - u32 invoice_get_partial_list_outgoing(u32 page_index, u32 page_size, invoice* buffer); - u32 invoice_get_partial_list_incomming(u32 page_index, u32 page_size, invoice* buffer); - u32 invoice_get_all(invoice* buffer); - a_err invoice_get_by_id(invoice* buffer, char* id); - u32 invoice_get_tax_rates(invoice* invoice, tax_rate* buffer); - bool invoice_get_subtotal_for_tax_rate(invoice* invoice, tax_rate rate, tax_subtotal* buffer); + u32 invoice_count(); + u32 invoice_get_incomming_count(); + u32 invoice_get_outgoing_count(); + invoice invoice_create_empty(); + a_err invoice_import(invoice* invoice); + a_err invoice_add(invoice* invoice); + a_err invoice_update(invoice* invoice); + a_err invoice_remove(invoice* invoice); + void invoice_set_currency(invoice* invoice, char* currency); + invoice invoice_create_copy(invoice* invoice); + void invoice_destroy(invoice* invoice); + a_err invoice_is_valid(invoice* invoice); + bool invoice_has_intra_community_services(invoice* invoice); + char* invoice_get_status_string(invoice* invoice); + u32 invoice_get_partial_list_outgoing(u32 page_index, u32 page_size, invoice* buffer); + u32 invoice_get_partial_list_incomming(u32 page_index, u32 page_size, invoice* buffer); + u32 invoice_get_all(invoice* buffer); + a_err invoice_get_by_id(invoice* buffer, char* id); + u32 invoice_get_tax_rates(invoice* invoice, tax_rate* buffer); + bool invoice_get_subtotal_for_tax_rate(invoice* invoice, tax_rate rate, tax_subtotal* buffer); // Billing item functions. // ======================= - u32 billing_item_count(invoice* invoice); + u32 billing_item_count(invoice* invoice); billing_item billing_item_create_empty(); - a_err billing_item_import_to_invoice(invoice* invoice, billing_item item); - a_err billing_item_add_to_invoice(invoice* invoice, billing_item item); - a_err billing_item_update_in_invoice(invoice* invoice, billing_item item); - a_err billing_item_remove_from_invoice(invoice* invoice, billing_item item); + a_err billing_item_import_to_invoice(invoice* invoice, billing_item item); + a_err billing_item_add_to_invoice(invoice* invoice, billing_item item); + a_err billing_item_update_in_invoice(invoice* invoice, billing_item item); + a_err billing_item_remove_from_invoice(invoice* invoice, billing_item item); tax_subtotal billing_item_convert_to_default_currency(invoice* invoice, billing_item item); - a_err billing_item_is_valid(billing_item item); + a_err billing_item_is_valid(billing_item item); + + u32 billing_item_get_all_for_invoice(invoice* invoice, billing_item* buffer); - u32 billing_item_get_all_for_invoice(invoice* invoice, billing_item* buffer); + // Activity functions. + // =================== + a_err activity_add(char* user, char* ref_id, char* message, ...); + u32 activity_get_all_for_object(activity* buffer, char* ref_id); } \ No newline at end of file diff --git a/src/administration.cpp b/src/administration.cpp index cb158e2..d7e4eed 100644 --- a/src/administration.cpp +++ b/src/administration.cpp @@ -129,6 +129,7 @@ void administration_create() list_init(&g_administration.contacts); list_init(&g_administration.projects); list_init(&g_administration.tax_rates); + list_init(&g_administration.activities); list_init(&g_administration.all_tax_rates); list_init(&g_administration.cost_centers); strops::copy(g_administration.path, "", sizeof(g_administration.path)); @@ -176,6 +177,18 @@ static void administration_destroy_invoices() list_iterator_stop(&g_administration.invoices); } +int sort_invoice_time_t(const void *a, const void *b) { + invoice* inv1 = (invoice*)a; + invoice* inv2 = (invoice*)b; + return (int)(inv1->issued_at - inv2->issued_at); +} + +void administration::sort_data() +{ + list_attributes_comparator(&g_administration.invoices, sort_invoice_time_t); + list_sort(&g_administration.invoices, 1); +} + void administration::destroy() { is_initialized = false; @@ -183,6 +196,7 @@ void administration::destroy() administration_destroy_list(&g_administration.invoices); administration_destroy_list(&g_administration.contacts); administration_destroy_list(&g_administration.projects); + administration_destroy_list(&g_administration.activities); administration_destroy_list(&g_administration.tax_rates); administration_destroy_list(&g_administration.all_tax_rates); administration_destroy_list(&g_administration.cost_centers); @@ -353,8 +367,8 @@ void administration::create_tax_statement(tax_statement* statement) time_t youngest = 0; for (u32 i = 0; i < invoice_count; i++) { - if (invoice_buffer[i].delivered_at < oldest) oldest = invoice_buffer[i].delivered_at; - if (invoice_buffer[i].delivered_at > youngest) youngest = invoice_buffer[i].delivered_at; + if (invoice_buffer[i].delivered_at < oldest) oldest = country::get_invoice_date_to_use_for_tax_report(country_code, &invoice_buffer[i]); + if (invoice_buffer[i].delivered_at > youngest) youngest = country::get_invoice_date_to_use_for_tax_report(country_code, &invoice_buffer[i]); } u16 oldest_year; @@ -527,7 +541,7 @@ void administration::create_income_statement(income_statement* statement) u16 yy; u8 qq; - time_t_to_quarter(inv->issued_at, &yy, &qq); + time_t_to_quarter(inv->delivered_at, &yy, &qq); u32 report_index = (qq + (yy*4)) - (oldest_quarter + (oldest_year*4)); @@ -1494,6 +1508,7 @@ a_err administration::invoice_update(invoice* inv) if (invoice_changed_event_callback) invoice_changed_event_callback(c); + administration::activity_add(ACTIVITY_USER, c->id, "activity.update_invoice", 0); return A_ERR_SUCCESS; } } @@ -1547,9 +1562,11 @@ a_err administration::invoice_add(invoice* inv) memops::copy(new_inv, ©, sizeof(invoice)); - if (!list_append(&g_administration.invoices, new_inv)) { + if (!list_prepend(&g_administration.invoices, new_inv)) { return A_ERR_GENERIC; - } + } + + administration::activity_add(ACTIVITY_USER, inv->id, "activity.add_invoice", 0); g_administration.next_id++; g_administration.next_sequence_number++; @@ -1900,4 +1917,50 @@ billing_item administration::billing_item_create_empty() memops::zero(&item, sizeof(billing_item)); item.amount = 1; return item; +} + +// Activity functions. +// =================== +a_err administration::activity_add(char* user, char* ref_id, char* message, ...) +{ + activity* new_activity = (activity*)memops::alloc(sizeof(activity)); + strops::copy(new_activity->user_name, user, MAX_LEN_SHORT_DESC); + strops::copy(new_activity->ref_id, ref_id, MAX_LEN_ID); + strops::copy(new_activity->message, message, MAX_LEN_LONG_DESC); + new_activity->timestamp = time(NULL); + + va_list args; + va_start(args, message); + + char* param; + u32 param_count = 0; + do + { + param = va_arg(args, char*); + if (param != 0) { + strops::copy(new_activity->params[param_count++], param, MAX_LEN_LONG_DESC); + } + } while (param && param_count < ACTIVITY_MAX_PARAMS); + + va_end(args); + + if (!list_prepend(&g_administration.activities, new_activity)) { + return A_ERR_GENERIC; + } + + return A_ERR_SUCCESS; +} + +u32 administration::activity_get_all_for_object(activity* buffer, char* ref_id) +{ + u32 write_cursor = 0; + + list_iterator_start(&g_administration.activities); + while (list_iterator_hasnext(&g_administration.activities)) { + activity c = *(activity *)list_iterator_next(&g_administration.activities); + if (strops::equals(c.ref_id, ref_id)) buffer[write_cursor++] = c; + } + list_iterator_stop(&g_administration.activities); + + return write_cursor; } \ No newline at end of file diff --git a/src/administration_reader.cpp b/src/administration_reader.cpp index 05133f8..34cfe8f 100644 --- a/src/administration_reader.cpp +++ b/src/administration_reader.cpp @@ -101,6 +101,8 @@ bool administration_reader::open_existing(char* file_path) zip_close(zip); + administration::sort_data(); + logger::info("Imported '%s' in %.3fms.", file_path, STOPWATCH_TIME); return true; diff --git a/src/administration_writer.cpp b/src/administration_writer.cpp index c699a0a..f12cf1f 100644 --- a/src/administration_writer.cpp +++ b/src/administration_writer.cpp @@ -357,7 +357,8 @@ static char* get_eas_id_for_contact(contact contact) if (strops::equals(country_code, "IT")) return contact.businessid; // Italy if (strops::equals(country_code, "FI")) return contact.businessid; // Finland if (strops::equals(country_code, "DK")) return contact.businessid; // Denmark - return NULL; // Unknown country code + + return contact.businessid; // Unknown country code } static char* get_eas_scheme_for_contact(contact contact) @@ -397,7 +398,8 @@ static char* get_eas_scheme_for_contact(contact contact) if (strops::equals(country_code, "SI")) return "9949"; // Slovenia if (strops::equals(country_code, "SK")) return "9950"; // Slovakia if (strops::equals(country_code, "ES")) return "9920"; // Spain - return NULL; // Unknown country code + + return "0203"; // Hack } bool isEmptyTag(const char *start, const char *end) { diff --git a/src/locales/en.cpp b/src/locales/en.cpp index d949609..752e328 100644 --- a/src/locales/en.cpp +++ b/src/locales/en.cpp @@ -54,6 +54,7 @@ locale_entry en_locales[] = { {"form.create", "+ Create"}, {"form.back", "Back"}, {"form.save", "Save"}, + {"form.cancel", "Cancel"}, {"form.yes", "Yes"}, {"form.no", "No"}, {"form.change", "Change"}, @@ -721,7 +722,7 @@ locale_entry en_locales[] = { {"taxrate.code.NL_IN/IEUV/21","Intra-EU reverse charge 21% VAT"}, {"taxrate.code.NL_IN/IEUV/9","Intra-EU reverse charge 9% VAT"}, - // Tax statement strings. + // NL Tax statement strings. {"taxes.total", "Total"}, {"taxes.nl.1", "Domestic supplies and services"}, @@ -746,6 +747,10 @@ locale_entry en_locales[] = { {"taxes.nl.5", "Input tax and small businesses scheme (KOR)"}, {"taxes.nl.5a", "VAT due (sections 1 to 4)"}, {"taxes.nl.5b", "Input tax (VAT deductible)"}, + + // Activity strings. + {"activity.update_invoice", "updated invoice"}, + {"activity.add_invoice", "created invoice"}, }; int en_locale_count = sizeof(en_locales) / sizeof(en_locales[0]); \ No newline at end of file diff --git a/src/ui/ui_expenses.cpp b/src/ui/ui_expenses.cpp index 5556cd6..3ea31e1 100644 --- a/src/ui/ui_expenses.cpp +++ b/src/ui/ui_expenses.cpp @@ -26,19 +26,34 @@ #include "administration.hpp" #include "administration_writer.hpp" +static activity* activity_buffer = 0; +static u32 activity_count; + static importer::invoice_request* active_import_request = 0; static ui::view_state current_view_state = ui::view_state::LIST_ALL; static invoice active_invoice = {0}; static invoice selected_for_removal = {0}; - +static const float sidepanel_width = 200.0f; static billing_item* invoice_items_buffer = 0; void draw_invoice_items_form(invoice* invoice, bool outgoing = true); +static void _reload_activities() +{ + activity_count = administration::activity_get_all_for_object(activity_buffer, active_invoice.id); +} + +static void _set_active_invoice(invoice inv) +{ + active_invoice = inv; + _reload_activities(); +} + void ui::destroy_expenses() { memops::unalloc(invoice_items_buffer); + memops::unalloc(activity_buffer); } void ui::setup_expenses() @@ -50,7 +65,8 @@ void ui::setup_expenses() current_view_state = ui::view_state::LIST_ALL; } - active_invoice = administration::invoice_create_empty(); + activity_buffer = (activity*)memops::alloc(sizeof(activity) * 250); + _set_active_invoice(administration::invoice_create_empty()); u32 invoice_items_count = MAX_BILLING_ITEMS; invoice_items_buffer = (billing_item*)memops::alloc(sizeof(billing_item) * invoice_items_count); @@ -215,22 +231,35 @@ static void draw_expenses_list() ImGui::Spacing(); - if (ImGui::BeginTable("TableInvoices", 7, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + if (ImGui::BeginTable("TableInvoices", 6, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { ImGui::TableSetupColumn(locale::get("invoice.table.invoicenumber"), ImGuiTableColumnFlags_WidthFixed, 120); ImGui::TableSetupColumn(locale::get("invoice.table.sender")); ImGui::TableSetupColumn(locale::get("invoice.table.customer")); - ImGui::TableSetupColumn(locale::get("invoice.table.issuedat")); - ImGui::TableSetupColumn(locale::get("invoice.table.total")); - ImGui::TableSetupColumn(locale::get("invoice.table.status")); - ImGui::TableSetupColumn(""); + ImGui::TableSetupColumn(locale::get("invoice.table.issuedat"), ImGuiTableColumnFlags_WidthFixed, 90); + ImGui::TableSetupColumn(locale::get("invoice.table.total"), ImGuiTableColumnFlags_WidthFixed, 90); + ImGui::TableSetupColumn(locale::get("invoice.table.status"), ImGuiTableColumnFlags_WidthFixed, 90); ImGui::TableHeadersRow(); for (u32 i = 0; i < invoice_count; i++) { invoice c = invoice_list[i]; ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); ImGui::Text(c.sequential_number); + ImGui::TableSetColumnIndex(0); + + ImGui::PushID(i); + ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap; + bool selected = false; + if (ImGui::Selectable("##invisible_selectable", selected, selectable_flags, ImVec2(0, ImGui::GetFrameHeight()-4.0f))) + { + _set_active_invoice(c); + current_view_state = ui::view_state::VIEW_EXISTING; + } + ImGui::SetItemAllowOverlap(); + ImGui::SameLine(); + ImGui::PopID(); + + ImGui::Text(c.sequential_number); if (administration::invoice_is_valid(&c) != A_ERR_SUCCESS) { @@ -249,8 +278,8 @@ static void draw_expenses_list() ImGui::TableSetColumnIndex(3); ImGui::Text(buf); ImGui::TableSetColumnIndex(4); ImGui::Text("%.2f %s", c.total, c.currency); ImGui::TableSetColumnIndex(5); ImGui::Text("%s", locale::get(administration::invoice_get_status_string(&c))); - ImGui::TableSetColumnIndex(6); + /* char btn_name[20]; strops::format(btn_name, sizeof(btn_name), "%s##%d", locale::get("form.view"), i); if (ImGui::Button(btn_name)) { @@ -271,23 +300,7 @@ static void draw_expenses_list() if (ImGui::Button(btn_name)) { selected_for_removal = c; ImGui::OpenPopup("ConfirmDeletePopup"); - } - } - - // Confirmation popup before contact is deleted definitively. - if (ImGui::BeginPopupModal("ConfirmDeletePopup", nullptr, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoTitleBar)) { - ImGui::Text(locale::get("form.confirmDelete")); - ImGui::Separator(); - - if (ImGui::Button(locale::get("form.yes"), ImVec2(120, 0))) { - administration::invoice_remove(&selected_for_removal); - ImGui::CloseCurrentPopup(); - } - ImGui::SameLine(); - if (ImGui::Button(locale::get("form.no"), ImVec2(120, 0))) { - ImGui::CloseCurrentPopup(); - } - ImGui::EndPopup(); + }*/ } ImGui::EndTable(); @@ -299,26 +312,75 @@ static void _reset_to_default_view() current_view_state = ui::view_state::LIST_ALL; ui::destroy_expenses(); ui::setup_expenses(); + activity_count = 0; +} + +static void _reset_to_invoice_view() +{ + current_view_state = ui::view_state::VIEW_EXISTING; +} + +static void _draw_activity_sidepanel() +{ + ImGui::SameLine(); + ImGui::SeparatorEx(1 << 1, 3.0f); + ImGui::SameLine(); + + ImGui::BeginChild("##historyPanel", ImVec2(sidepanel_width, 0), ImGuiChildFlags_None); + { + for (u32 i = 0; i < activity_count; i++) + { + activity ac = activity_buffer[i]; + + char time_buffer[26]; + struct tm* tm_info; + tm_info = localtime(&ac.timestamp); + strftime(time_buffer, 26, "%d/%m/%Y %H:%M", tm_info); + + ImGui::Text(time_buffer); + + ImGui::PushFont(ui::fontBold); + ImGui::Text(ac.user_name); + ImGui::PopFont(); + ImGui::SameLine(); + + ImGui::TextWrapped("%s", locale::get(ac.message)); + ImGui::Spacing(); + } + } + + ImGui::EndChild(); } static void draw_expense_update() { - if (ImGui::Button(locale::get("form.back"), true, false)) { - _reset_to_default_view(); + if (ImGui::Button(locale::get("form.cancel"), true, false)) { + current_view_state = ui::view_state::VIEW_EXISTING; } - - draw_expense_form(&active_invoice); - bool can_save = administration::invoice_is_valid(&active_invoice) == A_ERR_SUCCESS; - if (!can_save) ImGui::BeginDisabled(); - + { // Save button + bool can_save = administration::invoice_is_valid(&active_invoice) == A_ERR_SUCCESS; + if (!can_save) ImGui::BeginDisabled(); + ImGui::SameLine(); + if (ImGui::Button(locale::get("form.save"), true)) { + administration_writer::set_write_completed_event_callback(_reset_to_invoice_view); + administration::invoice_update(&active_invoice); + + _reload_activities(); + } + if (!can_save) ImGui::EndDisabled(); + } + + ImGui::Spacing(); + ImGui::Separator(3.0f); ImGui::Spacing(); - if (ImGui::Button(locale::get("form.save"), true)) { - administration_writer::set_write_completed_event_callback(_reset_to_default_view); - administration::invoice_update(&active_invoice); - } + + float availableWidth = ImGui::GetContentRegionAvail().x; + ImGui::BeginChild("##invoicePanel", ImVec2(availableWidth - sidepanel_width, 0), ImGuiChildFlags_None); + draw_expense_form(&active_invoice); + ImGui::EndChild(); - if (!can_save) ImGui::EndDisabled(); + _draw_activity_sidepanel(); } static void draw_expense_create() @@ -326,19 +388,28 @@ static void draw_expense_create() if (ImGui::Button(locale::get("form.back"), true, false)) { _reset_to_default_view(); } - - draw_expense_form(&active_invoice); - bool can_save = administration::invoice_is_valid(&active_invoice) == A_ERR_SUCCESS; - if (!can_save) ImGui::BeginDisabled(); - - ImGui::Spacing(); - if (ImGui::Button(locale::get("form.save"), true)) { - administration_writer::set_write_completed_event_callback(_reset_to_default_view); - administration::invoice_add(&active_invoice); - } + { // Save button + bool can_save = administration::invoice_is_valid(&active_invoice) == A_ERR_SUCCESS; + if (!can_save) ImGui::BeginDisabled(); + ImGui::SameLine(); + if (ImGui::Button(locale::get("form.save"), true)) { + administration_writer::set_write_completed_event_callback(_reset_to_invoice_view); + administration::invoice_add(&active_invoice); + + _reload_activities(); + } + if (!can_save) ImGui::EndDisabled(); + } - if (!can_save) ImGui::EndDisabled(); + ImGui::Spacing(); + ImGui::Separator(3.0f); + ImGui::Spacing(); + + float availableWidth = ImGui::GetContentRegionAvail().x; + ImGui::BeginChild("##invoicePanel", ImVec2(availableWidth - sidepanel_width, 0), ImGuiChildFlags_None); + draw_expense_form(&active_invoice); + ImGui::EndChild(); } static void draw_expense_view() @@ -347,7 +418,23 @@ static void draw_expense_view() _reset_to_default_view(); } + ImGui::SameLine(); + + if (ImGui::Button(locale::get("form.change"))) { + _set_active_invoice(administration::invoice_create_copy(&active_invoice)); // We create a copy because of billing item list pointers. + current_view_state = ui::view_state::EDIT_EXISTING; + } + + ImGui::Spacing(); + ImGui::Separator(3.0f); + ImGui::Spacing(); + + float availableWidth = ImGui::GetContentRegionAvail().x; + ImGui::BeginChild("##invoicePanel", ImVec2(availableWidth - sidepanel_width, 0), ImGuiChildFlags_None); draw_expense_form(&active_invoice, true); + ImGui::EndChild(); + + _draw_activity_sidepanel(); } static void draw_import_request() diff --git a/src/ui/ui_invoices.cpp b/src/ui/ui_invoices.cpp index 0e81259..ff3c73b 100644 --- a/src/ui/ui_invoices.cpp +++ b/src/ui/ui_invoices.cpp @@ -25,23 +25,38 @@ #include "administration.hpp" #include "administration_writer.hpp" +static activity* activity_buffer = 0; +static u32 activity_count; static ui::view_state current_view_state = ui::view_state::LIST_ALL; static invoice active_invoice = {0}; static invoice selected_for_removal = {0}; static const float sidepanel_width = 200.0f; - static billing_item* invoice_items_buffer = 0; +static void _reload_activities() +{ + activity_count = administration::activity_get_all_for_object(activity_buffer, active_invoice.id); +} + +static void _set_active_invoice(invoice inv) +{ + active_invoice = inv; + _reload_activities(); +} + void ui::destroy_invoices() { memops::unalloc(invoice_items_buffer); + memops::unalloc(activity_buffer); } void ui::setup_invoices() { + activity_buffer = (activity*)memops::alloc(sizeof(activity) * 250); + current_view_state = ui::view_state::LIST_ALL; - active_invoice = administration::invoice_create_empty(); + _set_active_invoice(administration::invoice_create_empty()); u32 invoice_items_count = MAX_BILLING_ITEMS; invoice_items_buffer = (billing_item*)memops::alloc(sizeof(billing_item) * invoice_items_count); @@ -279,7 +294,7 @@ static void draw_invoices_list() if (ImGui::Button(locale::get("form.create"))) { current_view_state = ui::view_state::CREATE; - active_invoice = administration::invoice_create_empty(); // @leak + _set_active_invoice(administration::invoice_create_empty()); active_invoice.supplier = administration::company_info_get(); active_invoice.is_outgoing = 1; active_invoice.status = invoice_status::INVOICE_CONCEPT; @@ -328,7 +343,7 @@ static void draw_invoices_list() bool selected = false; if (ImGui::Selectable("##invisible_selectable", selected, selectable_flags, ImVec2(0, ImGui::GetFrameHeight()-4.0f))) { - active_invoice = c; + _set_active_invoice(c); current_view_state = ui::view_state::VIEW_EXISTING; } ImGui::SetItemAllowOverlap(); @@ -372,11 +387,44 @@ static void _reset_to_default_view() current_view_state = ui::view_state::LIST_ALL; ui::destroy_invoices(); ui::setup_invoices(); + activity_count = 0; +} + +static void _draw_activity_sidepanel() +{ + ImGui::SameLine(); + ImGui::SeparatorEx(1 << 1, 3.0f); + ImGui::SameLine(); + + ImGui::BeginChild("##historyPanel", ImVec2(sidepanel_width, 0), ImGuiChildFlags_None); + { + for (u32 i = 0; i < activity_count; i++) + { + activity ac = activity_buffer[i]; + + char time_buffer[26]; + struct tm* tm_info; + tm_info = localtime(&ac.timestamp); + strftime(time_buffer, 26, "%d/%m/%Y %H:%M", tm_info); + + ImGui::Text(time_buffer); + + ImGui::PushFont(ui::fontBold); + ImGui::Text(ac.user_name); + ImGui::PopFont(); + ImGui::SameLine(); + + ImGui::TextWrapped("%s", locale::get(ac.message)); + ImGui::Spacing(); + } + } + + ImGui::EndChild(); } static void draw_invoice_update() { - if (ImGui::Button(locale::get("form.back"), true, false)) { + if (ImGui::Button(locale::get("form.cancel"), true, false)) { current_view_state = ui::view_state::VIEW_EXISTING; } @@ -387,6 +435,8 @@ static void draw_invoice_update() if (ImGui::Button(locale::get("form.save"), true)) { administration_writer::set_write_completed_event_callback(_reset_to_invoice_view); administration::invoice_update(&active_invoice); + + _reload_activities(); } if (!can_save) ImGui::EndDisabled(); } @@ -399,6 +449,8 @@ static void draw_invoice_update() ImGui::BeginChild("##invoicePanel", ImVec2(availableWidth - sidepanel_width, 0), ImGuiChildFlags_None); draw_invoice_form(&active_invoice); ImGui::EndChild(); + + _draw_activity_sidepanel(); } static void draw_invoice_create() @@ -414,6 +466,8 @@ static void draw_invoice_create() if (ImGui::Button(locale::get("form.save"), true)) { administration_writer::set_write_completed_event_callback(_reset_to_invoice_view); administration::invoice_add(&active_invoice); + + _reload_activities(); } if (!can_save) ImGui::EndDisabled(); } @@ -437,7 +491,7 @@ static void draw_invoice_view() ImGui::SameLine(); if (ImGui::Button(locale::get("form.change"))) { - active_invoice = administration::invoice_create_copy(&active_invoice); // We create a copy because of billing item list pointers. + _set_active_invoice(administration::invoice_create_copy(&active_invoice)); // We create a copy because of billing item list pointers. current_view_state = ui::view_state::EDIT_EXISTING; } @@ -480,13 +534,7 @@ static void draw_invoice_view() draw_invoice_form(&active_invoice, true); ImGui::EndChild(); - ImGui::SameLine(); - ImGui::SeparatorEx(1 << 1, 3.0f); - ImGui::SameLine(); - - ImGui::BeginChild("##historyPanel", ImVec2(sidepanel_width, 0), ImGuiChildFlags_None); - // Sidepanel - ImGui::EndChild(); + _draw_activity_sidepanel(); } void ui::draw_invoices() -- cgit v1.2.3-70-g09d2