diff options
| author | Aldrik Ramaekers <aldrikboy@gmail.com> | 2025-08-08 20:34:22 +0200 |
|---|---|---|
| committer | Aldrik Ramaekers <aldrikboy@gmail.com> | 2025-08-08 20:34:22 +0200 |
| commit | 21496e32695744d4679fc11105352c61522ce601 (patch) | |
| tree | a4bda5abe657f25f5d9054a055d5cdfe93ca64f1 | |
| parent | 550b3bf614d8eeb116cceadf3e180ca7a4490976 (diff) | |
contact crud
| -rw-r--r-- | NOTES.md | 11 | ||||
| -rw-r--r-- | src/administration.cpp | 65 | ||||
| -rw-r--r-- | src/administration.hpp | 18 | ||||
| -rw-r--r-- | src/locales/en.cpp | 33 | ||||
| -rw-r--r-- | src/main.cpp | 6 | ||||
| -rw-r--r-- | src/views/contacts.cpp | 184 | ||||
| -rw-r--r-- | src/views/dashboard.cpp | 9 | ||||
| -rw-r--r-- | src/views/views.hpp | 4 |
8 files changed, 251 insertions, 79 deletions
@@ -56,19 +56,21 @@ administration.money/ | id `auto` | reference id `C/[id]` | | name `required` | Full name of individual or company name | | address line 1 `required` | Address | -| address line 2 `required` | Zip, place, country | +| address line 2 `required` | Zip, place | +| country `required` | Country | | tax number | Tax identification number | | business number | Business number | | email | Email address | | Phone number | Phone number | | bank account | Bank account number (to display on outgoing invoices) | -| Invoice|| +| Invoice [docs](https://accountancyeurope.eu/wp-content/uploads/2025/04/250423-VAT-and-the-Digital-Age-Factsheet-Accountancy-Europe.pdf)|| |-|-| | id `auto` | reference id `I/[id]` | -| invoice number `auto` | Generated invoice number | +| sequential number `auto` | Generated sequential invoice number | | is outgoing `required` | incoming or outgoing invoice | -| contact `required` | Contact information, stored as reference `C/[id]` | +| customer `required` | Customer contact information, stored as reference `C/[id]` | +| supplier `required` | Supplier contact information, stored as reference `C/[id]` | | issue at `required` | Date when invoice was issued | | delivered at `required` | Date when goods or services were delivered | | expires at `required` | Date when invoice expires | @@ -86,6 +88,7 @@ administration.money/ | keep untill `required` | Date untill invoice needs to be stored legally | | payment on account date | If advance payment received and differs from invoice date (Ireland only) | | tax representative | If supplier uses tax representative in another Member State (Belgium only) | +| corrected sequential number | for corrective invoices, the sequential number that identifies the original invoice to be corrected | | Billing item|| |-|-| diff --git a/src/administration.cpp b/src/administration.cpp index 94b1910..052f007 100644 --- a/src/administration.cpp +++ b/src/administration.cpp @@ -1,18 +1,19 @@ #include "administration.hpp" -#include <stdio.h> + administration g_administration; -void init_administration_obj() +void administration_create() { list_init(&g_administration.contacts); + strncpy(g_administration.path, "[unsaved project]", sizeof(g_administration.path)); // @localize } -void destroy_administration_obj() +void administration_destroy() { list_destroy(&g_administration.contacts); } -bool create_contact(contact data) +bool administration_create_contact(contact data) { contact* new_contact = (contact*)malloc(sizeof(contact)); memcpy((void*)new_contact, (void*)&data, sizeof(contact)); @@ -23,7 +24,7 @@ bool create_contact(contact data) return true; } -bool update_contact(contact data) +bool administration_update_contact(contact data) { list_iterator_start(&g_administration.contacts); while (list_iterator_hasnext(&g_administration.contacts)) { @@ -31,6 +32,24 @@ bool update_contact(contact data) if (strcmp(c->id, data.id) == 0) { memcpy(c, &data, sizeof(data)); + list_iterator_stop(&g_administration.contacts); + return true; + } + } + list_iterator_stop(&g_administration.contacts); + + return false; +} + +bool administration_remove_contact(contact data) +{ + list_iterator_start(&g_administration.contacts); + while (list_iterator_hasnext(&g_administration.contacts)) { + contact* c = (contact *)list_iterator_next(&g_administration.contacts); + + if (strcmp(c->id, data.id) == 0) { + list_iterator_stop(&g_administration.contacts); + list_delete(&g_administration.contacts, c); return true; } } @@ -39,7 +58,39 @@ bool update_contact(contact data) return false; } -void remove_contact(int index) +s32 administration_create_id() +{ + return g_administration.next_id; +} + +u32 administration_get_contact_count() +{ + return list_size(&g_administration.contacts); +} + +u32 administration_get_contacts(u32 page_index, u32 page_size, contact* buffer) +{ + assert(buffer); + + u32 write_cursor = 0; + + u32 read_start = page_index * page_size; + + list_iterator_start(&g_administration.contacts); + while (list_iterator_hasnext(&g_administration.contacts)) { + contact c = *(contact *)list_iterator_next(&g_administration.contacts); + + if (g_administration.contacts.iter_pos <= read_start) continue; + + buffer[write_cursor++] = c; + if (write_cursor >= page_size) break; + } + list_iterator_stop(&g_administration.contacts); + + return write_cursor; +} + +char* administration_get_file_path() { - list_delete_at(&g_administration.contacts, index); + return g_administration.path; }
\ No newline at end of file diff --git a/src/administration.hpp b/src/administration.hpp index 9bf790c..c20bbfb 100644 --- a/src/administration.hpp +++ b/src/administration.hpp @@ -2,6 +2,8 @@ #include <string.h> #include <stdlib.h> +#include <stdio.h> +#include <assert.h> #include "config.hpp" #include "simclist.h" @@ -12,6 +14,7 @@ typedef struct char name[64]; char address1[128]; char address2[128]; + char country[128]; char taxid[32]; char businessid[32]; char email[64]; @@ -35,11 +38,14 @@ typedef struct char email_key[32]; } administration; -extern administration g_administration; +void administration_create(); +void administration_destroy(); -void init_administration_obj(); -void destroy_administration_obj(); +bool administration_remove_contact(contact data); +bool administration_create_contact(contact data); +bool administration_update_contact(contact data); -void remove_contact(int index); -bool create_contact(contact data); -bool update_contact(contact data);
\ No newline at end of file +char* administration_get_file_path(); +s32 administration_create_id(); +u32 administration_get_contact_count(); +u32 administration_get_contacts(u32 page_index, u32 page_size, contact* buffer); // Buffer size atleast be page_size * sizeof contact
\ No newline at end of file diff --git a/src/locales/en.cpp b/src/locales/en.cpp index d23a766..de33655 100644 --- a/src/locales/en.cpp +++ b/src/locales/en.cpp @@ -8,10 +8,40 @@ locale_entry en_locales[] = { {"form.yes", "Yes"}, {"form.no", "No"}, {"form.change", "Change"}, + {"form.view", "View"}, {"form.delete", "Delete"}, {"form.confirmDelete", "Are you sure you want to delete this item?"}, {"form.required", "required"}, + // Countries + { "country.AT", "Austria" }, + { "country.BE", "Belgium" }, + { "country.BG", "Bulgaria" }, + { "country.HR", "Croatia" }, + { "country.CY", "Cyprus" }, + { "country.CZ", "Czech Republic" }, + { "country.DK", "Denmark" }, + { "country.EE", "Estonia" }, + { "country.FI", "Finland" }, + { "country.FR", "France" }, + { "country.DE", "Germany" }, + { "country.GR", "Greece" }, + { "country.HU", "Hungary" }, + { "country.IE", "Ireland" }, + { "country.IT", "Italy" }, + { "country.LV", "Latvia" }, + { "country.LT", "Lithuania" }, + { "country.LU", "Luxembourg" }, + { "country.MT", "Malta" }, + { "country.NL", "Netherlands" }, + { "country.PL", "Poland" }, + { "country.PT", "Portugal" }, + { "country.RO", "Romania" }, + { "country.SK", "Slovakia" }, + { "country.SI", "Slovenia" }, + { "country.ES", "Spain" }, + { "country.SE", "Sweden" }, + // Navigation. {"nav.invoices", "Invoices"}, {"nav.expenses", "Expenses"}, @@ -25,7 +55,8 @@ locale_entry en_locales[] = { {"contact.form.identifier", "Identifier"}, {"contact.form.fullname", "Full name / name of business"}, {"contact.form.address1", "Street name + house number, appt. number, etc."}, - {"contact.form.address2", "Zip, city, country"}, + {"contact.form.address2", "Zip, city"}, + {"contact.form.country", "Country"}, {"contact.form.taxnumber", "Tax number"}, {"contact.form.businessnumber", "Business number"}, {"contact.form.email", "Email address"}, diff --git a/src/main.cpp b/src/main.cpp index 323a5a0..3f6cf03 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -87,7 +87,7 @@ int main() ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); - init_administration_obj(); + administration_create(); // Main loop bool done = false; @@ -128,7 +128,7 @@ int main() ImGui_ImplWin32_NewFrame(); ImGui::NewFrame(); - show_dashboard(); + views_draw_dashboard(); // Rendering ImGui::Render(); @@ -143,7 +143,7 @@ int main() g_SwapChainOccluded = (hr == DXGI_STATUS_OCCLUDED); } - destroy_administration_obj(); + administration_destroy(); // Cleanup ImGui_ImplDX11_Shutdown(); diff --git a/src/views/contacts.cpp b/src/views/contacts.cpp index 2ce300d..4b352bc 100644 --- a/src/views/contacts.cpp +++ b/src/views/contacts.cpp @@ -5,16 +5,16 @@ #include "../administration.hpp" typedef enum { - VIEW, + LIST, EDIT, CREATE, - INSPECT, + VIEW, } contact_view_state; -contact_view_state view_state = VIEW; -static int selected_for_removal = -1; // Index in contact list selected for removal. +static contact_view_state view_state = LIST; +static contact selected_for_removal; -static contact edit_contact; +static contact active_contact; static void draw_required_tag() { @@ -25,7 +25,7 @@ static void draw_required_tag() ImVec2 text_size = ImGui::CalcTextSize(text); text_pos.y += text_size.y/4.0f; - ImVec4 bg_color = ImVec4(0.9f, 0.235f, 0.235f, 0.4f); // Blue background + ImVec4 bg_color = ImVec4(0.9f, 0.235f, 0.235f, 0.4f); // Red background ImVec4 text_color = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // White text float rounding = 2.0f; float padding = 2.0f; @@ -41,83 +41,143 @@ static void draw_required_tag() ImGui::PopStyleColor(); } -static void show_edit_contact() +static void draw_contact_form() { + static const char* selected_country = NULL; + if (ImGui::Button(localize("form.back"))) { - view_state = contact_view_state::VIEW; - memset(&edit_contact, 0, sizeof(contact)); + view_state = contact_view_state::LIST; + memset(&active_contact, 0, sizeof(contact)); + selected_country = 0; } ImGui::Spacing(); - // Input fields + bool viewing_only = (view_state == contact_view_state::VIEW); + ImGui::BeginDisabled(); float widthAvailable = ImGui::GetContentRegionAvail().x; ImGui::SetNextItemWidth(widthAvailable*0.2f); - ImGui::InputText(localize("contact.form.identifier"), edit_contact.id, IM_ARRAYSIZE(edit_contact.id)); - ImGui::EndDisabled(); + ImGui::InputText(localize("contact.form.identifier"), active_contact.id, IM_ARRAYSIZE(active_contact.id)); + if (!viewing_only) ImGui::EndDisabled(); + + ImGui::SetNextItemWidth(widthAvailable*0.5f); + ImGui::InputTextWithHint(localize("contact.form.fullname"), localize("contact.form.fullname"), active_contact.name, IM_ARRAYSIZE(active_contact.name)); + ImGui::SameLine();draw_required_tag(); ImGui::SetNextItemWidth(widthAvailable*0.5f); - ImGui::InputTextWithHint(localize("contact.form.fullname"), localize("contact.form.fullname"), edit_contact.name, IM_ARRAYSIZE(edit_contact.name)); + ImGui::InputTextWithHint(localize("contact.form.address1"), localize("contact.form.address1"), active_contact.address1, IM_ARRAYSIZE(active_contact.address1)); ImGui::SameLine();draw_required_tag(); ImGui::SetNextItemWidth(widthAvailable*0.5f); - ImGui::InputTextWithHint(localize("contact.form.address1"), localize("contact.form.address1"), edit_contact.address1, IM_ARRAYSIZE(edit_contact.address1)); + ImGui::InputTextWithHint(localize("contact.form.address2"), localize("contact.form.address2"), active_contact.address2, IM_ARRAYSIZE(active_contact.address2)); ImGui::SameLine();draw_required_tag(); ImGui::SetNextItemWidth(widthAvailable*0.5f); - ImGui::InputTextWithHint(localize("contact.form.address2"), localize("contact.form.address2"), edit_contact.address2, IM_ARRAYSIZE(edit_contact.address2)); + + const char* countries[] = { localize("country.AT"),localize("country.BE"),localize("country.BG"),localize("country.HR"),localize("country.CY"),localize("country.CZ"),localize("country.DK"),localize("country.EE"),localize("country.FI"),localize("country.FR"),localize("country.DE"),localize("country.GR"),localize("country.HU"),localize("country.IE"),localize("country.IT"),localize("country.LV"),localize("country.LT"),localize("country.LU"),localize("country.MT"),localize("country.NL"),localize("country.PL"),localize("country.PT"),localize("country.RO"),localize("country.SK"),localize("country.SI"),localize("country.ES"),localize("country.SE") }; + s32 country_count = sizeof(countries) / sizeof(countries[0]); + if (selected_country == 0) { + for (int i = 0; i < country_count; i++) + { + if (strcmp(countries[i], active_contact.country) == 0) + { + selected_country = countries[i]; + break; + } + } + } + + if (ImGui::BeginCombo(localize("contact.form.country"), selected_country)) + { + for (int n = 0; n < IM_ARRAYSIZE(countries); n++) + { + bool is_selected = (selected_country == countries[n]); + if (ImGui::Selectable(countries[n], is_selected)) + selected_country = countries[n]; + } + ImGui::EndCombo(); + } + if (selected_country) { + strncpy(active_contact.country, selected_country, IM_ARRAYSIZE(active_contact.country)); + } ImGui::SameLine();draw_required_tag(); ImGui::SetNextItemWidth(widthAvailable*0.5f); - ImGui::InputTextWithHint(localize("contact.form.taxnumber"), localize("contact.form.taxnumber"), edit_contact.taxid, IM_ARRAYSIZE(edit_contact.taxid)); + ImGui::InputTextWithHint(localize("contact.form.taxnumber"), localize("contact.form.taxnumber"), active_contact.taxid, IM_ARRAYSIZE(active_contact.taxid)); ImGui::SetNextItemWidth(widthAvailable*0.5f); - ImGui::InputTextWithHint(localize("contact.form.businessnumber"), localize("contact.form.businessnumber"), edit_contact.businessid, IM_ARRAYSIZE(edit_contact.businessid)); + ImGui::InputTextWithHint(localize("contact.form.businessnumber"), localize("contact.form.businessnumber"), active_contact.businessid, IM_ARRAYSIZE(active_contact.businessid)); ImGui::SetNextItemWidth(widthAvailable*0.5f); - ImGui::InputTextWithHint(localize("contact.form.email"), localize("contact.form.email"), edit_contact.email, IM_ARRAYSIZE(edit_contact.email)); + ImGui::InputTextWithHint(localize("contact.form.email"), localize("contact.form.email"), active_contact.email, IM_ARRAYSIZE(active_contact.email)); ImGui::SetNextItemWidth(widthAvailable*0.5f); - ImGui::InputTextWithHint(localize("contact.form.phonenumber"), localize("contact.form.phonenumber"), edit_contact.phone_number, IM_ARRAYSIZE(edit_contact.phone_number)); + ImGui::InputTextWithHint(localize("contact.form.phonenumber"), localize("contact.form.phonenumber"), active_contact.phone_number, IM_ARRAYSIZE(active_contact.phone_number)); ImGui::SetNextItemWidth(widthAvailable*0.5f); - ImGui::InputTextWithHint(localize("contact.form.bankaccount"), localize("contact.form.bankaccount"), edit_contact.bank_account, IM_ARRAYSIZE(edit_contact.bank_account)); + ImGui::InputTextWithHint(localize("contact.form.bankaccount"), localize("contact.form.bankaccount"), active_contact.bank_account, IM_ARRAYSIZE(active_contact.bank_account)); - bool can_save = strlen(edit_contact.name) > 0 && strlen(edit_contact.address1) > 0 && strlen(edit_contact.address2) > 0; + if (viewing_only) ImGui::EndDisabled(); - if (!can_save) ImGui::BeginDisabled(); + if (!viewing_only) { + bool can_save = strlen(active_contact.name) > 0 && strlen(active_contact.address1) > 0 && + strlen(active_contact.address2) > 0 && strlen(active_contact.country) > 0; - // Save button - ImGui::Spacing(); - if (ImGui::Button(localize("form.save"))) { - if (view_state == contact_view_state::CREATE) - create_contact(edit_contact); + if (!can_save) ImGui::BeginDisabled(); + // Save button + ImGui::Spacing(); + if (ImGui::Button(localize("form.save"))) { + if (view_state == contact_view_state::CREATE) + administration_create_contact(active_contact); - else if (view_state == contact_view_state::EDIT) - update_contact(edit_contact); + else if (view_state == contact_view_state::EDIT) + administration_update_contact(active_contact); - memset(&edit_contact, 0, sizeof(contact)); - view_state = contact_view_state::VIEW; + memset(&active_contact, 0, sizeof(contact)); + view_state = contact_view_state::LIST; + selected_country = 0; + } + if (!can_save) ImGui::EndDisabled(); + } + else { + // TODO list invoices connected to contact. } - - if (!can_save) ImGui::EndDisabled(); } -void show_contacts() +static void draw_contact_list() { - if (view_state == contact_view_state::CREATE || view_state == contact_view_state::EDIT) { - show_edit_contact(); - return; - } - + const u32 items_per_page = 5; + static s32 current_page = 0; + s32 max_page = (administration_get_contact_count() + items_per_page - 1) / items_per_page; + if (max_page == 0) max_page = 1; + if (ImGui::Button(localize("form.create"))) { view_state = contact_view_state::CREATE; - memset(&edit_contact, 0, sizeof(contact)); - snprintf(edit_contact.id, IM_ARRAYSIZE(edit_contact.id), "C/%d", g_administration.next_id); + memset(&active_contact, 0, sizeof(contact)); + snprintf(active_contact.id, IM_ARRAYSIZE(active_contact.id), "C/%d", administration_create_id()); } + + if (current_page >= max_page-1) current_page = max_page-1; + if (current_page < 0) current_page = 0; + + ImGui::SameLine(); + bool enable_prev = current_page > 0; + if (!enable_prev) ImGui::BeginDisabled(); + if (ImGui::Button("<< Prev") && current_page > 0) current_page--; + if (!enable_prev) ImGui::EndDisabled(); + + ImGui::SameLine(); + ImGui::Text("(%d/%d)", current_page+1, max_page); + + ImGui::SameLine(); + bool enable_next = current_page < max_page-1; + if (!enable_next) ImGui::BeginDisabled(); + if (ImGui::Button("Next >>") && current_page < max_page-1) current_page++; + if (!enable_next) ImGui::EndDisabled(); + ImGui::Spacing(); if (ImGui::BeginTable("TableContacts", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { @@ -125,12 +185,14 @@ void show_contacts() ImGui::TableSetupColumn(localize("contact.table.identifier"), ImGuiTableColumnFlags_WidthFixed, 80); ImGui::TableSetupColumn(localize("contact.table.name"), ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn(localize("contact.table.address"), ImGuiTableColumnFlags_WidthStretch); - ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 120); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 160); ImGui::TableHeadersRow(); - list_iterator_start(&g_administration.contacts); - while (list_iterator_hasnext(&g_administration.contacts)) { - contact c = *(contact *)list_iterator_next(&g_administration.contacts); + contact contact_list[items_per_page]; + u32 contact_count = administration_get_contacts(current_page, items_per_page, contact_list); + + for (u32 i = 0; i < contact_count; i++) { + contact c = contact_list[i]; ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); ImGui::Text(c.id); @@ -140,29 +202,36 @@ void show_contacts() ImGui::TableSetColumnIndex(3); char btn_name[20]; - sprintf(btn_name, "%s##%d", localize("form.change"), g_administration.contacts.iter_pos); + sprintf(btn_name, "%s##%d", localize("form.view"), i); if (ImGui::Button(btn_name)) { - edit_contact = c; + active_contact = c; + view_state = contact_view_state::VIEW; + } + + ImGui::SameLine(); + + sprintf(btn_name, "%s##%d", localize("form.change"), i); + if (ImGui::Button(btn_name)) { + active_contact = c; view_state = contact_view_state::EDIT; } ImGui::SameLine(); - // @TODO check to make sure no invoices are connected to this contact. - sprintf(btn_name, "%s##%d", localize("form.delete"), g_administration.contacts.iter_pos); + // TODO check to make sure no invoices are connected to this contact. + sprintf(btn_name, "%s##%d", localize("form.delete"), i); if (ImGui::Button(btn_name)) { - selected_for_removal = g_administration.contacts.iter_pos; + selected_for_removal = c; ImGui::OpenPopup("ConfirmDeletePopup"); } } - list_iterator_stop(&g_administration.contacts); if (ImGui::BeginPopupModal("ConfirmDeletePopup", nullptr, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoTitleBar)) { ImGui::Text(localize("form.confirmDelete")); ImGui::Separator(); if (ImGui::Button(localize("form.yes"), ImVec2(120, 0))) { - remove_contact(selected_for_removal-1); + administration_remove_contact(selected_for_removal); ImGui::CloseCurrentPopup(); } ImGui::SameLine(); @@ -174,4 +243,15 @@ void show_contacts() ImGui::EndTable(); } +} + +void views_draw_contacts() +{ + switch(view_state) + { + case contact_view_state::LIST: draw_contact_list(); break; + case contact_view_state::CREATE: draw_contact_form(); break; + case contact_view_state::EDIT: draw_contact_form(); break; + case contact_view_state::VIEW: draw_contact_form(); break; + } }
\ No newline at end of file diff --git a/src/views/dashboard.cpp b/src/views/dashboard.cpp index 45658b0..86dc18f 100644 --- a/src/views/dashboard.cpp +++ b/src/views/dashboard.cpp @@ -1,6 +1,6 @@ #include "views.hpp" - #include "imgui.h" +#include "../administration.hpp" typedef enum { @@ -18,14 +18,15 @@ static dashboard_view_state view_state = dashboard_view_state::INVOICES; void (*drawcalls[dashboard_view_state::END])(void) = { 0, 0, - show_contacts, + views_draw_contacts, 0, 0, 0, }; -void show_dashboard() +void views_draw_dashboard() { + // @localize if (ImGui::BeginMainMenuBar()) { if (ImGui::BeginMenu("File")) @@ -101,7 +102,7 @@ void show_dashboard() ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoCollapse); - ImGui::Text("Working on: []"); + ImGui::Text("Working on: %s", administration_get_file_path()); ImGui::SameLine(); ImGui::Text("Status: []"); diff --git a/src/views/views.hpp b/src/views/views.hpp index adefea2..2ebdedd 100644 --- a/src/views/views.hpp +++ b/src/views/views.hpp @@ -2,5 +2,5 @@ #include "../locales/locales.hpp" -void show_dashboard(); -void show_contacts();
\ No newline at end of file +void views_draw_dashboard(); +void views_draw_contacts();
\ No newline at end of file |
