summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAldrik Ramaekers <aldrikboy@gmail.com>2025-08-10 19:03:46 +0200
committerAldrik Ramaekers <aldrikboy@gmail.com>2025-08-10 19:03:46 +0200
commit572caa74ed824fefa02eb81adc7639a783f243c7 (patch)
tree9938efe957a708a3642c19a03fdacaaa2b0618d5
parent3c83a2d06cf33429ca0e654a415fc95581df46e1 (diff)
working on invoice form
-rw-r--r--docs/README.rst5
-rw-r--r--include/administration.hpp4
-rw-r--r--include/strops.hpp3
-rw-r--r--include/ui.hpp2
-rw-r--r--libs/ImGuiDatePicker/ImGuiDatePicker.cpp393
-rw-r--r--libs/ImGuiDatePicker/ImGuiDatePicker.hpp19
-rw-r--r--libs/ImGuiDatePicker/Images/AltFontDemoImage.pngbin0 -> 25829 bytes
-rw-r--r--libs/ImGuiDatePicker/Images/DemoGif.gifbin0 -> 473676 bytes
-rw-r--r--libs/ImGuiDatePicker/Images/DemoImage.pngbin0 -> 23349 bytes
-rw-r--r--libs/ImGuiDatePicker/LICENSE21
-rw-r--r--libs/ImGuiDatePicker/README.md77
-rw-r--r--run.bat4
-rw-r--r--src/administration.cpp106
-rw-r--r--src/strops.cpp17
-rw-r--r--src/ui/helpers.cpp14
-rw-r--r--src/ui/ui_contacts.cpp31
-rw-r--r--src/ui/ui_invoices.cpp81
-rw-r--r--src/ui/ui_projects.cpp2
18 files changed, 738 insertions, 41 deletions
diff --git a/docs/README.rst b/docs/README.rst
index 7dedc8d..4affde6 100644
--- a/docs/README.rst
+++ b/docs/README.rst
@@ -36,5 +36,6 @@ This section lists all country specific implementations to be compliant. If the
3. Dependencies
---------------
-- ImGui 1.92.1
-- simclist 1.5 \ No newline at end of file
+- ImGui 1.92.1 (https://github.com/ocornut/imgui)
+- simclist 1.5 (https://mij.oltrelinux.com/devel/simclist/)
+- ImGuiDatePicker (https://github.com/DnA-IntRicate/ImGuiDatePicker) \ No newline at end of file
diff --git a/include/administration.hpp b/include/administration.hpp
index 68deb78..0917ee5 100644
--- a/include/administration.hpp
+++ b/include/administration.hpp
@@ -95,8 +95,8 @@ typedef struct
char customer_id[16];
char supplier_id[16];
time_t issued_at;
- time_t delivered_at;
time_t expires_at;
+ time_t delivered_at;
char document[255];
list_t billing_items;
char project_id[16];
@@ -147,6 +147,7 @@ u32 administration_get_contact_count();
u32 administration_get_contacts(u32 page_index, u32 page_size, contact* buffer);
contact administration_create_empty_contact();
bool administration_is_contact_valid(contact data);
+int administration_get_contact_recommendations(contact* buffer, int buf_size, char* name);
void administration_cancel_project(project data);
bool administration_remove_project(project data);
@@ -155,6 +156,7 @@ bool administration_update_project(project data);
char* administration_project_get_status_string(project data);
u32 administration_get_project_count();
u32 administration_get_projects(u32 page_index, u32 page_size, project* buffer);
+bool administration_is_project_valid(project data);
project administration_create_empty_project();
u32 administration_get_tax_bracket_count();
diff --git a/include/strops.hpp b/include/strops.hpp
index daa1d71..08dbd5a 100644
--- a/include/strops.hpp
+++ b/include/strops.hpp
@@ -1,3 +1,4 @@
#pragma once
-size_t strops_copy(char *dst, const char *src, size_t size); \ No newline at end of file
+size_t strops_copy(char *dst, const char *src, size_t size);
+char* strops_stristr(char* a, char* b); \ No newline at end of file
diff --git a/include/ui.hpp b/include/ui.hpp
index 7a02329..5c5b6fc 100644
--- a/include/ui.hpp
+++ b/include/ui.hpp
@@ -11,7 +11,7 @@ typedef enum
void ui_helper_draw_required_tag();
void ui_helper_show_toast(const char* msg);
void ui_helper_draw_toasts();
-void ui_helper_TextInputWithAutocomplete(const char* label, const char* hint, char* buffer, size_t buf_size, char** suggestions, int suggestion_count);
+int ui_helper_TextInputWithAutocomplete(const char* label, const char* hint, char* buffer, size_t buf_size, char** suggestions, int suggestion_count);
void ui_draw_main();
void ui_draw_contacts();
diff --git a/libs/ImGuiDatePicker/ImGuiDatePicker.cpp b/libs/ImGuiDatePicker/ImGuiDatePicker.cpp
new file mode 100644
index 0000000..8c81b2b
--- /dev/null
+++ b/libs/ImGuiDatePicker/ImGuiDatePicker.cpp
@@ -0,0 +1,393 @@
+#include "ImGuiDatePicker.hpp"
+#include <imgui_internal.h>
+#include <cstdint>
+#include <chrono>
+#include <vector>
+#include <unordered_map>
+
+
+#define GET_DAY(timePoint) int(timePoint.tm_mday)
+#define GET_MONTH_UNSCALED(timePoint) timePoint.tm_mon
+#define GET_MONTH(timePoint) int(GET_MONTH_UNSCALED(timePoint) + 1)
+#define GET_YEAR(timePoint) int(timePoint.tm_year + 1900)
+
+#define SET_DAY(timePoint, day) timePoint.tm_mday = day
+#define SET_MONTH(timePoint, month) timePoint.tm_mon = month - 1
+#define SET_YEAR(timePoint, year) timePoint.tm_year = year - 1900
+
+namespace ImGui
+{
+ static const std::vector<std::string> MONTHS =
+ {
+ "January",
+ "February",
+ "March",
+ "April",
+ "May",
+ "June",
+ "July",
+ "August",
+ "September",
+ "October",
+ "November",
+ "December"
+ };
+
+ static const std::vector<std::string> DAYS =
+ {
+ "Mo",
+ "Tu",
+ "We",
+ "Th",
+ "Fr",
+ "Sa",
+ "Su"
+ };
+
+ // Implements Zeller's Congruence to determine the day of week [1, 7](Mon-Sun) from the given parameters
+ inline static int DayOfWeek(int dayOfMonth, int month, int year) noexcept
+ {
+ if ((month == 1) || (month == 2))
+ {
+ month += 12;
+ year -= 1;
+ }
+
+ int h = (dayOfMonth
+ + static_cast<int>(std::floor((13 * (month + 1)) / 5.0))
+ + year
+ + static_cast<int>(std::floor(year / 4.0))
+ - static_cast<int>(std::floor(year / 100.0))
+ + static_cast<int>(std::floor(year / 400.0))) % 7;
+
+ return static_cast<int>(std::floor(((h + 5) % 7) + 1));
+ }
+
+ constexpr static bool IsLeapYear(int year) noexcept
+ {
+ if ((year % 400) == 0)
+ return true;
+
+ if ((year % 4 == 0) && ((year % 100) != 0))
+ return true;
+
+ return false;
+ }
+
+ inline static int NumDaysInMonth(int month, int year)
+ {
+ if (month == 2)
+ return IsLeapYear(year) ? 29 : 28;
+
+ // Month index paired to the number of days in that month excluding February
+ static const std::unordered_map<int, int> monthDayMap =
+ {
+ { 1, 31 },
+ { 3, 31 },
+ { 4, 30 },
+ { 5, 31 },
+ { 6, 30 },
+ { 7, 31 },
+ { 8, 31 },
+ { 9, 30 },
+ { 10, 31 },
+ { 11, 30 },
+ { 12, 31 }
+ };
+
+ return monthDayMap.at(month);
+ }
+
+ // Returns the number of calendar weeks spanned by month in the specified year
+ inline static int NumWeeksInMonth(int month, int year)
+ {
+ int days = NumDaysInMonth(month, year);
+ int firstDay = DayOfWeek(1, month, year);
+
+ return static_cast<int>(std::ceil((days + firstDay - 1) / 7.0));
+ }
+
+ // Returns a vector containing dates as they would appear on the calendar for a given week. Populates 0 if there is no day.
+ inline static std::vector<int> CalendarWeek(int week, int startDay, int daysInMonth)
+ {
+ std::vector<int> res(7, 0);
+ int startOfWeek = 7 * (week - 1) + 2 - startDay;
+
+ if (startOfWeek >= 1)
+ res[0] = startOfWeek;
+
+ for (int i = 1; i < 7; ++i)
+ {
+ int day = startOfWeek + i;
+ if ((day >= 1) && (day <= daysInMonth))
+ res[i] = day;
+ }
+
+ return res;
+ }
+
+ constexpr static tm EncodeTimePoint(int dayOfMonth, int month, int year) noexcept
+ {
+ tm res{ };
+ res.tm_isdst = -1;
+ SET_DAY(res, dayOfMonth);
+ SET_MONTH(res, month);
+ SET_YEAR(res, year);
+
+ return res;
+ }
+
+ inline static std::string TimePointToLongString(const tm& timePoint) noexcept
+ {
+ std::string day = std::to_string(GET_DAY(timePoint));
+ std::string month = MONTHS[GET_MONTH_UNSCALED(timePoint)];
+ std::string year = std::to_string(GET_YEAR(timePoint));
+
+ return std::string(day + " " + month + " " + year);
+ }
+
+ inline static tm Today() noexcept
+ {
+ std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
+ std::time_t currentTime = std::chrono::system_clock::to_time_t(now);
+
+ tm res;
+ gmtime_s(&res, &currentTime);
+
+ return res;
+ }
+
+ inline static tm PreviousMonth(const tm& timePoint) noexcept
+ {
+ int month = GET_MONTH(timePoint);
+ int year = GET_YEAR(timePoint);
+
+ if (month == 1)
+ {
+ int newDay = std::min(GET_DAY(timePoint), NumDaysInMonth(12, --year));
+ return EncodeTimePoint(newDay, 12, year);
+ }
+
+ int newDay = std::min(GET_DAY(timePoint), NumDaysInMonth(--month, year));
+ return EncodeTimePoint(newDay, month, year);
+ }
+
+ inline static tm NextMonth(const tm& timePoint) noexcept
+ {
+ int month = GET_MONTH(timePoint);
+ int year = GET_YEAR(timePoint);
+
+ if (month == 12)
+ {
+ int newDay = std::min(GET_DAY(timePoint), NumDaysInMonth(1, ++year));
+ return EncodeTimePoint(newDay, 1, year);
+ }
+
+ int newDay = std::min(GET_DAY(timePoint), NumDaysInMonth(++month, year));
+ return EncodeTimePoint(newDay, month, year);
+ }
+
+ constexpr static bool IsMinDate(const tm& timePoint) noexcept
+ {
+ return (GET_MONTH(timePoint) == 1) && (GET_YEAR(timePoint) == IMGUI_DATEPICKER_YEAR_MIN);
+ }
+
+ constexpr static bool IsMaxDate(const tm& timePoint) noexcept
+ {
+ return (GET_MONTH(timePoint) == 12) && (GET_YEAR(timePoint) == IMGUI_DATEPICKER_YEAR_MAX);
+ }
+
+ static bool ComboBox(const std::string& label, const std::vector<std::string>& items, int& v, ImFont* altFont)
+ {
+ bool res = false;
+
+ ImGui::PushFont(altFont);
+ if (ImGui::BeginCombo(label.c_str(), items[v].c_str()))
+ {
+ for (int i = 0; i < items.size(); ++i)
+ {
+ bool selected = (items[v] == items[i]);
+ if (ImGui::Selectable(items[i].c_str(), &selected))
+ {
+ v = i;
+ res = true;
+ }
+
+ if (selected)
+ ImGui::SetItemDefaultFocus();
+ }
+
+ ImGui::EndCombo();
+ }
+
+ ImGui::PopFont();
+ return res;
+ }
+
+ bool DatePickerEx(const std::string& label, tm& v, ImFont* altFont, bool clampToBorder, float itemSpacing)
+ {
+ bool res = false;
+
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return false;
+
+ bool hiddenLabel = label.substr(0, 2) == "##";
+ std::string myLabel = (hiddenLabel) ? label.substr(2) : label;
+
+ if (!hiddenLabel)
+ {
+ Text("%s", label.c_str());
+ SameLine((itemSpacing == 0.0f) ? 0.0f : GetCursorPos().x + itemSpacing);
+ }
+
+ if (clampToBorder)
+ SetNextItemWidth(GetContentRegionAvail().x);
+
+ const ImVec2 windowSize = ImVec2(274.5f, 301.5f);
+ SetNextWindowSize(windowSize);
+
+ if (BeginCombo(std::string("##" + myLabel).c_str(), TimePointToLongString(v).c_str()))
+ {
+ int monthIdx = GET_MONTH_UNSCALED(v);
+ int year = GET_YEAR(v);
+
+ PushItemWidth((GetContentRegionAvail().x * 0.5f));
+
+ if (ComboBox("##CmbMonth_" + myLabel, MONTHS, monthIdx, altFont))
+ {
+ SET_MONTH(v, monthIdx + 1);
+ res = true;
+ }
+
+ PopItemWidth();
+ SameLine();
+ PushItemWidth(GetContentRegionAvail().x);
+
+ if (InputInt(std::string("##IntYear_" + myLabel).c_str(), &year))
+ {
+ SET_YEAR(v, std::min(std::max(IMGUI_DATEPICKER_YEAR_MIN, year), IMGUI_DATEPICKER_YEAR_MAX));
+ res = true;
+ }
+
+ PopItemWidth();
+
+ const float contentWidth = GetContentRegionAvail().x;
+ const float arrowSize = GetFrameHeight();
+ const float arrowButtonWidth = arrowSize * 2.0f + GetStyle().ItemSpacing.x;
+ const float bulletSize = arrowSize - 5.0f;
+ const float bulletButtonWidth = bulletSize + GetStyle().ItemSpacing.x;
+ const float combinedWidth = arrowButtonWidth + bulletButtonWidth;
+ const float offset = (contentWidth - combinedWidth) * 0.5f;
+
+ SetCursorPosX(GetCursorPosX() + offset);
+ PushStyleVar(ImGuiStyleVar_FrameRounding, 20.0f);
+ PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
+ PushStyleColor(ImGuiCol_Border, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
+ BeginDisabled(IsMinDate(v));
+
+ if (ArrowButtonEx(std::string("##ArrowLeft_" + myLabel).c_str(), ImGuiDir_Left, ImVec2(arrowSize, arrowSize)))
+ {
+ v = PreviousMonth(v);
+ res = true;
+ }
+
+ EndDisabled();
+ PopStyleColor(2);
+ SameLine();
+ PushStyleColor(ImGuiCol_Button, GetStyleColorVec4(ImGuiCol_Text));
+ SetCursorPosY(GetCursorPosY() + 2.0f);
+
+ if (ButtonEx(std::string("##ArrowMid_" + myLabel).c_str(), ImVec2(bulletSize, bulletSize)))
+ {
+ v = Today();
+ res = true;
+ CloseCurrentPopup();
+ }
+
+ PopStyleColor();
+ SameLine();
+ PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
+ PushStyleColor(ImGuiCol_Border, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
+ BeginDisabled(IsMaxDate(v));
+
+ if (ArrowButtonEx(std::string("##ArrowRight_" + myLabel).c_str(), ImGuiDir_Right, ImVec2(arrowSize, arrowSize)))
+ {
+ v = NextMonth(v);
+ res = true;
+ }
+
+ EndDisabled();
+ PopStyleColor(2);
+ PopStyleVar();
+
+ constexpr ImGuiTableFlags TABLE_FLAGS = ImGuiTableFlags_BordersOuter | ImGuiTableFlags_SizingFixedFit |
+ ImGuiTableFlags_NoHostExtendX | ImGuiTableFlags_NoHostExtendY;
+
+ if (BeginTable(std::string("##Table_" + myLabel).c_str(), 7, TABLE_FLAGS, GetContentRegionAvail()))
+ {
+ for (const auto& day : DAYS)
+ TableSetupColumn(day.c_str(), ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoHeaderWidth, 30.0f);
+
+ PushStyleColor(ImGuiCol_HeaderHovered, GetStyleColorVec4(ImGuiCol_TableHeaderBg));
+ PushStyleColor(ImGuiCol_HeaderActive, GetStyleColorVec4(ImGuiCol_TableHeaderBg));
+ PushFont(altFont);
+ TableHeadersRow();
+ PopStyleColor(2);
+ PopFont();
+
+ TableNextRow();
+ TableSetColumnIndex(0);
+
+ int month = monthIdx + 1;
+ int firstDayOfMonth = DayOfWeek(1, month, year);
+ int numDaysInMonth = NumDaysInMonth(month, year);
+ int numWeeksInMonth = NumWeeksInMonth(month, year);
+
+ for (int i = 1; i <= numWeeksInMonth; ++i)
+ {
+ for (const auto& day : CalendarWeek(i, firstDayOfMonth, numDaysInMonth))
+ {
+ if (day != 0)
+ {
+ PushStyleVar(ImGuiStyleVar_FrameRounding, 20.0f);
+
+ const bool selected = day == GET_DAY(v);
+ if (!selected)
+ {
+ PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
+ PushStyleColor(ImGuiCol_Border, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
+ }
+
+ if (Button(std::to_string(day).c_str(), ImVec2(GetContentRegionAvail().x, GetTextLineHeightWithSpacing() + 5.0f)))
+ {
+ v = EncodeTimePoint(day, month, year);
+ res = true;
+ CloseCurrentPopup();
+ }
+
+ if (!selected)
+ PopStyleColor(2);
+
+ PopStyleVar();
+ }
+
+ if (day != numDaysInMonth)
+ TableNextColumn();
+ }
+ }
+
+ EndTable();
+ }
+
+ EndCombo();
+ }
+
+ return res;
+ }
+
+ bool DatePicker(const std::string& label, tm& v, bool clampToBorder, float itemSpacing)
+ {
+ return DatePickerEx(label, v, nullptr, clampToBorder, itemSpacing);
+ }
+}
diff --git a/libs/ImGuiDatePicker/ImGuiDatePicker.hpp b/libs/ImGuiDatePicker/ImGuiDatePicker.hpp
new file mode 100644
index 0000000..91ed569
--- /dev/null
+++ b/libs/ImGuiDatePicker/ImGuiDatePicker.hpp
@@ -0,0 +1,19 @@
+#pragma once
+#include <imgui.h>
+#include <string>
+
+
+#ifndef IMGUI_DATEPICKER_YEAR_MIN
+ #define IMGUI_DATEPICKER_YEAR_MIN 1900
+#endif // !IMGUI_DATEPICKER_YEAR_MIN
+
+#ifndef IMGUI_DATEPICKER_YEAR_MAX
+ #define IMGUI_DATEPICKER_YEAR_MAX 3000
+#endif // !IMGUI_DATEPICKER_YEAR_MAX
+
+namespace ImGui
+{
+ IMGUI_API bool DatePickerEx(const std::string& label, tm& v, ImFont* altFont, bool clampToBorder = false, float itemSpacing = 130.0f);
+
+ IMGUI_API bool DatePicker(const std::string& label, tm& v, bool clampToBorder = false, float itemSpacing = 130.0f);
+}
diff --git a/libs/ImGuiDatePicker/Images/AltFontDemoImage.png b/libs/ImGuiDatePicker/Images/AltFontDemoImage.png
new file mode 100644
index 0000000..ee12d8d
--- /dev/null
+++ b/libs/ImGuiDatePicker/Images/AltFontDemoImage.png
Binary files differ
diff --git a/libs/ImGuiDatePicker/Images/DemoGif.gif b/libs/ImGuiDatePicker/Images/DemoGif.gif
new file mode 100644
index 0000000..7e54364
--- /dev/null
+++ b/libs/ImGuiDatePicker/Images/DemoGif.gif
Binary files differ
diff --git a/libs/ImGuiDatePicker/Images/DemoImage.png b/libs/ImGuiDatePicker/Images/DemoImage.png
new file mode 100644
index 0000000..0cd03dc
--- /dev/null
+++ b/libs/ImGuiDatePicker/Images/DemoImage.png
Binary files differ
diff --git a/libs/ImGuiDatePicker/LICENSE b/libs/ImGuiDatePicker/LICENSE
new file mode 100644
index 0000000..66090be
--- /dev/null
+++ b/libs/ImGuiDatePicker/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 Adam Foflonker
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/libs/ImGuiDatePicker/README.md b/libs/ImGuiDatePicker/README.md
new file mode 100644
index 0000000..3da59ab
--- /dev/null
+++ b/libs/ImGuiDatePicker/README.md
@@ -0,0 +1,77 @@
+# ImGuiDatePicker
+ImGuiDatePicker is a custom calendar-style date-picker widget for [Dear ImGui](https://github.com/ocornut/imgui) written in `C++20` that can easily be compiled alongside `ImGui` by adding the header and source files to your `ImGui` project.
+
+![DemoGif](Images/DemoGif.gif)
+Font used: [Open Sans](https://fonts.google.com/specimen/Open+Sans)
+
+ImGuiDatePicker uses the `tm` struct for working with dates:
+``` C++
+struct tm
+{
+ int tm_sec; // seconds after the minute - [0, 60] including leap second
+ int tm_min; // minutes after the hour - [0, 59]
+ int tm_hour; // hours since midnight - [0, 23]
+ int tm_mday; // day of the month - [1, 31]
+ int tm_mon; // months since January - [0, 11]
+ int tm_year; // years since 1900
+ int tm_wday; // days since Sunday - [0, 6]
+ int tm_yday; // days since January 1 - [0, 365]
+ int tm_isdst; // daylight savings time flag
+};
+```
+## Including
+`IMGUI_DATEPICKER_YEAR_MIN` and `IMGUI_DATEPICKER_YEAR_MAX` should be defined before including [ImGuiDatePicker.hpp](ImGuiDatePicker.hpp). If they are not defined, they will default to `1900` and `3000` respectively.
+``` C++
+// Define the lowest year that the picker can select. In this example, '1970' is the Unix epoch.
+#define IMGUI_DATEPICKER_YEAR_MIN 1970
+// Define the highest year that the picker can select.
+#define IMGUI_DATEPICKER_YEAR_MAX 3000
+#include <ImGuiDatePicker.hpp>
+```
+## Usage
+``` C++
+// Get today's date and store it in a 'tm' struct named 't'
+std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
+std::time_t currentTime = std::chrono::system_clock::to_time_t(now);
+tm t = *std::gmtime(&currentTime);
+
+// Use the picker
+if (ImGui::DatePicker("Date", t))
+{
+ // Perform some event whenever the date 't' is changed
+}
+```
+An `alt font` can also be supplied to the picker by calling `DatePickerEx`. This `alt font` will be used for the picker's days-of-the-week headers and for the month-picker combo box.
+``` C++
+ImFont* boldFont = GetBoldFontFromSomewhere();
+tm t = GetTmFromSomewhere();
+ImGui::DatePickerEx("Date", t, boldFont);
+```
+![AltFontDemoImage](Images/AltFontDemoImage.png)
+
+## License
+ImGuiDatePicker is licensed under the MIT License. See [LICENSE](LICENSE).
+
+```
+MIT License
+
+Copyright (c) 2024 Adam Foflonker
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+```
diff --git a/run.bat b/run.bat
index 7197b94..f557bb4 100644
--- a/run.bat
+++ b/run.bat
@@ -17,11 +17,11 @@ if not exist "%VCVARSALL%" (
call "%VCVARSALL%" x64
@set OUT_DIR=build\\
@set OUT_EXE=accounting
-@set LIB_SOURCES=libs\imgui-1.92.1\backends\imgui_impl_dx11.cpp libs\imgui-1.92.1\backends\imgui_impl_win32.cpp libs\imgui-1.92.1\imgui*.cpp libs\simclist-1.5\simclist.c
+@set LIB_SOURCES=libs\imgui-1.92.1\backends\imgui_impl_dx11.cpp libs\imgui-1.92.1\backends\imgui_impl_win32.cpp libs\imgui-1.92.1\imgui*.cpp libs\simclist-1.5\simclist.c libs\ImGuiDatePicker\*.cpp
@set SOURCES= src\*.cpp src\ui\*.cpp src\locales\*.cpp
@set LIBS=opengl32.lib Advapi32.lib Shell32.lib Ole32.lib User32.lib Pathcch.lib D3D11.lib
@set FLAGS=/nologo /Ob0 /MT /Oy- /Zi /FS /W4 /EHsc /utf-8
-@set INCLUDE_DIRS=/I"libs/imgui-1.92.1" /I"libs/imgui-1.92.1/backends" /I"/" /I"libs/simclist-1.5" /Iinclude
+@set INCLUDE_DIRS=/I"libs/imgui-1.92.1" /I"libs/imgui-1.92.1/backends" /I"/" /I"libs/simclist-1.5" /I"libs/" /Iinclude
cl %FLAGS% %INCLUDE_DIRS% %SOURCES% %LIB_SOURCES% /Fe%OUT_DIR%/%OUT_EXE%.exe /Fd%OUT_DIR%/vc140.pdb /Fo%OUT_DIR%/ /link %LIBS%
if "%1"=="-r" call "%OUT_DIR%/%OUT_EXE%.exe"
diff --git a/src/administration.cpp b/src/administration.cpp
index 869855e..a34ece8 100644
--- a/src/administration.cpp
+++ b/src/administration.cpp
@@ -199,6 +199,71 @@ static void administration_create_default_cost_centers()
ADD_COSTCENTER("costcenter.other_specialized", "OTHR");
}
+static void administration_create_debug_data()
+{
+ #define ADD_CONSUMER(_name, _addr1, _addr2, _cc)\
+ {contact _c = administration_create_empty_contact();\
+ strops_copy(_c.name, _name, sizeof(_c.name));\
+ strops_copy(_c.address.address1, _addr1, sizeof(_c.address.address1));\
+ strops_copy(_c.address.address2, _addr2, sizeof(_c.address.address2));\
+ strops_copy(_c.address.country_code, _cc, sizeof(_c.address.country_code));\
+ _c.type = contact_type::CONTACT_CONSUMER;\
+ administration_create_contact(_c);};
+
+ #define ADD_BUSINESS(_name, _addr1, _addr2, _cc, _tc, _bc)\
+ {contact _c = administration_create_empty_contact();\
+ strops_copy(_c.name, _name, sizeof(_c.name));\
+ strops_copy(_c.address.address1, _addr1, sizeof(_c.address.address1));\
+ strops_copy(_c.address.address2, _addr2, sizeof(_c.address.address2));\
+ strops_copy(_c.address.country_code, _cc, sizeof(_c.address.country_code));\
+ strops_copy(_c.taxid, _tc, sizeof(_c.taxid));\
+ strops_copy(_c.businessid, _bc, sizeof(_c.businessid));\
+ _c.type = contact_type::CONTACT_BUSINESS;\
+ administration_create_contact(_c);};
+
+ #define ADD_PROJECT(_name)\
+ {project _c = administration_create_empty_project();\
+ strops_copy(_c.description, _name, sizeof(_c.description));\
+ administration_create_project(_c);};
+
+ ADD_CONSUMER("Emma Müller", "Hauptstraße 12", "10115 Berlin", "DE");
+ ADD_CONSUMER("Luca Rossi", "Via Roma 45", "00184 Roma", "IT");
+ ADD_CONSUMER("Sofia Garcia", "Calle Mayor 7", "28013 Madrid", "ES");
+ ADD_CONSUMER("Jean Dupont", "10 Rue de la Paix", "75002 Paris", "FR");
+ ADD_CONSUMER("Anna Nowak", "ul. Kwiatowa 3", "00-001 Warszawa", "PL");
+ ADD_CONSUMER("Mikkel Jensen", "Østergade 8", "8000 Aarhus", "DK");
+ ADD_CONSUMER("Maria Svensson", "Kungsgatan 15", "111 22 Stockholm", "SE");
+ ADD_CONSUMER("Péter Kovács", "Fő utca 25", "1051 Budapest", "HU");
+ ADD_CONSUMER("Lucas Silva", "Rua Augusta 100", "1250-001 Lisboa", "PT");
+ ADD_CONSUMER("Isabelle Lefevre", "5 Place Stanislas", "54000 Nancy", "FR");
+
+ ADD_BUSINESS("Schmidt & Co GmbH", "Friedrichstraße 45", "10117 Berlin", "DE", "DE123456789", "HRB123456");
+ ADD_BUSINESS("Bianchi Srl", "Corso Venezia 12", "20121 Milano", "IT", "IT987654321", "MI1234567");
+ ADD_BUSINESS("Fernández y Asociados", "Gran Vía 20", "28013 Madrid", "ES", "ES456789123", "CIFB123456");
+ ADD_BUSINESS("Martin & Partners", "12 Avenue Victor Hugo", "75016 Paris", "FR", "FR321654987", "SIRET123456");
+ ADD_BUSINESS("Zielińska Consulting", "ul. Marszałkowska 10", "00-590 Warszawa", "PL", "PL789123456", "REGON123456");
+ ADD_BUSINESS("Sørensen ApS", "Strøget 3", "1460 København", "DK", "DK654321789", "CVR12345678");
+ ADD_BUSINESS("Johansson AB", "Drottninggatan 22", "111 51 Stockholm", "SE", "SE987654321", "OrgNr1234567");
+ ADD_BUSINESS("Nagy Kft.", "Andrássy út 60", "1062 Budapest", "HU", "HU123987654", "Cégjegyzékszám123");
+ ADD_BUSINESS("Santos Lda.", "Avenida da Liberdade 50", "1250-142 Lisboa", "PT", "PT321789654", "NIPC123456789");
+ ADD_BUSINESS("Dupuis SARL", "8 Rue Saint-Denis", "75001 Paris", "FR", "FR456123789", "SIREN123456");
+ ADD_BUSINESS("Müller & Söhne GmbH", "Leipziger Platz 8", "10117 Berlin", "DE", "DE654987321", "HRB987654");
+ ADD_BUSINESS("Romano Srl", "Via Garibaldi 14", "16124 Genova", "IT", "IT321654987", "GE1239876");
+ ADD_BUSINESS("López Asociados", "Plaza del Pilar 6", "50003 Zaragoza", "ES", "ES789321654", "CIFC654321");
+ ADD_BUSINESS("Laurent & Fils", "15 Boulevard Haussmann", "75009 Paris", "FR", "FR987321654", "SIRET654987");
+ ADD_BUSINESS("Kowalczyk Sp. z o.o.", "ul. Piotrkowska 55", "90-001 Łódź", "PL", "PL123456789", "REGON654321");
+ ADD_BUSINESS("Nielsen ApS", "Nørregade 12", "1165 København", "DK", "DK789456123", "CVR87654321");
+ ADD_BUSINESS("Lindberg AB", "Vasagatan 18", "111 20 Stockholm", "SE", "SE456789123", "OrgNr7654321");
+ ADD_BUSINESS("Szabó Kft.", "Kossuth Lajos tér 1", "1055 Budapest", "HU", "HU987123654", "Cégjegyzékszám654321");
+ ADD_BUSINESS("Costa Lda.", "Rua do Ouro 24", "1100-063 Lisboa", "PT", "PT654123987", "NIPC987654321");
+ ADD_BUSINESS("Moreau SARL", "3 Place de la République", "75011 Paris", "FR", "FR321456987", "SIREN789123");
+
+ ADD_PROJECT("eCommerce");
+ ADD_PROJECT("Retail store #1");
+ ADD_PROJECT("Retail store #2");
+ ADD_PROJECT("Kayak rental");
+}
+
void administration_create()
{
g_administration.next_id = 1;
@@ -220,6 +285,7 @@ void administration_create()
administration_create_default_tax_brackets();
administration_create_default_cost_centers();
+ administration_create_debug_data();
}
void administration_destroy()
@@ -232,6 +298,8 @@ void administration_destroy()
bool administration_create_contact(contact data)
{
+ if (!administration_is_contact_valid(data)) return false;
+
contact* new_contact = (contact*)malloc(sizeof(contact));
memcpy((void*)new_contact, (void*)&data, sizeof(contact));
list_append(&g_administration.contacts, new_contact);
@@ -250,6 +318,8 @@ bool administration_can_contact_be_deleted(contact data)
bool administration_update_contact(contact data)
{
+ if (!administration_is_contact_valid(data)) return false;
+
list_iterator_start(&g_administration.contacts);
while (list_iterator_hasnext(&g_administration.contacts)) {
contact* c = (contact *)list_iterator_next(&g_administration.contacts);
@@ -309,6 +379,25 @@ u32 administration_get_contacts(u32 page_index, u32 page_size, contact* buffer)
return write_cursor;
}
+int administration_get_contact_recommendations(contact* buffer, int buf_size, char* name)
+{
+ int write_cursor = 0;
+ if (name[0] == '\0') return 0;
+
+ list_iterator_start(&g_administration.contacts);
+ while (list_iterator_hasnext(&g_administration.contacts)) {
+ contact c = *(contact *)list_iterator_next(&g_administration.contacts);
+
+ if (strops_stristr(c.name, name)) {
+ buffer[write_cursor++] = c;
+ if (write_cursor >= buf_size) break;
+ }
+ }
+ list_iterator_stop(&g_administration.contacts);
+
+ return write_cursor;
+}
+
char* administration_get_file_path()
{
return g_administration.path;
@@ -348,6 +437,11 @@ void administration_cancel_project(project data)
administration_update_project(data);
}
+bool administration_is_project_valid(project data)
+{
+ return strlen(data.description) > 0;
+}
+
char* administration_project_get_status_string(project data)
{
switch(data.state)
@@ -362,6 +456,8 @@ char* administration_project_get_status_string(project data)
bool administration_create_project(project data)
{
+ if (!administration_is_project_valid(data)) return false;
+
data.state = project_state::PROJECT_RUNNING;
data.start_date = time(NULL);
data.end_date = 0;
@@ -376,6 +472,8 @@ bool administration_create_project(project data)
bool administration_update_project(project data)
{
+ if (!administration_is_project_valid(data)) return false;
+
list_iterator_start(&g_administration.projects);
while (list_iterator_hasnext(&g_administration.projects)) {
project* c = (project *)list_iterator_next(&g_administration.projects);
@@ -572,12 +670,20 @@ static s32 administration_create_sequence_number()
return g_administration.next_sequence_number;
}
+static time_t administration_get_default_invoice_expire_duration()
+{
+ return (30 * 24 * 60 * 60); // 30 days
+}
+
invoice administration_create_empty_invoice()
{
invoice result;
memset(&result, 0, sizeof(invoice));
snprintf(result.id, sizeof(result.id), "I/%d", administration_create_id());
snprintf(result.sequential_number, sizeof(result.id), "INV%010d", administration_create_sequence_number());
+ result.issued_at = time(NULL);
+ result.delivered_at = time(NULL);
+ result.expires_at = time(NULL) + administration_get_default_invoice_expire_duration();
return result;
}
diff --git a/src/strops.cpp b/src/strops.cpp
index 42042cb..116eec4 100644
--- a/src/strops.cpp
+++ b/src/strops.cpp
@@ -1,4 +1,5 @@
#include <string.h>
+#include <ctype.h>
#include "strops.hpp"
@@ -15,4 +16,20 @@ size_t strops_copy(char *dst, const char *src, size_t size)
dst[srclen] = '\0';
return (srclen);
+}
+
+char* strops_stristr(char* haystack, char* needle)
+{
+ do {
+ const char* h = haystack;
+ const char* n = needle;
+ while (tolower((unsigned char) *h) == tolower((unsigned char ) *n) && *n) {
+ h++;
+ n++;
+ }
+ if (*n == 0) {
+ return (char *) haystack;
+ }
+ } while (*haystack++);
+ return 0;
} \ No newline at end of file
diff --git a/src/ui/helpers.cpp b/src/ui/helpers.cpp
index a239f4c..4c6647e 100644
--- a/src/ui/helpers.cpp
+++ b/src/ui/helpers.cpp
@@ -65,20 +65,24 @@ void ui_helper_draw_required_tag()
ImGui::PopStyleColor();
}
-void ui_helper_TextInputWithAutocomplete(const char* label, const char* hint, char* buffer, size_t buf_size,
+int ui_helper_TextInputWithAutocomplete(const char* label, const char* hint, char* buffer, size_t buf_size,
char* suggestions[], int suggestion_count)
{
+ int result = -1;
static bool is_open = false;
ImGui::InputTextWithHint(label, hint, buffer, buf_size);
+ if (buffer[0] == '\0' && is_open) is_open = false;
+ if (suggestion_count == 0 && is_open) is_open = false;
+
bool is_active = ImGui::IsItemActive();
- if (is_active && buffer[0] != '\0')
+ if (is_active && buffer[0] != '\0' && suggestion_count > 0)
{
is_open = true;
}
if (is_open) {
- ImGui::BeginChild("autocomplete_popup", ImVec2(0, 100), true);
+ ImGui::BeginChild("autocomplete_popup", ImVec2(0, 10.0f + suggestion_count*23.0f), true);
{
ImVec2 win_pos = ImGui::GetWindowPos();
ImVec2 win_size = ImGui::GetWindowSize();
@@ -97,7 +101,7 @@ void ui_helper_TextInputWithAutocomplete(const char* label, const char* hint, ch
// Copy selected suggestion to buffer
strops_copy(buffer, suggestions[i], buf_size);
buffer[buf_size - 1] = '\0';
-
+ result = i;
is_open = false;
}
}
@@ -105,4 +109,6 @@ void ui_helper_TextInputWithAutocomplete(const char* label, const char* hint, ch
ImGui::EndChild();
}
}
+ return result;
}
+
diff --git a/src/ui/ui_contacts.cpp b/src/ui/ui_contacts.cpp
index c381db5..d57776e 100644
--- a/src/ui/ui_contacts.cpp
+++ b/src/ui/ui_contacts.cpp
@@ -18,9 +18,8 @@ void ui_setup_contacts()
memset(&selected_for_removal, 0, sizeof(contact));
}
-void draw_contact_form(contact* buffer, bool viewing_only = false)
+void draw_contact_form_ex(contact* buffer, bool viewing_only = false, bool with_autocomplete = false, bool* on_autocomplete = 0)
{
- bool with_autocomplete = false;
const char* selected_country = NULL;
ImGui::Spacing();
@@ -37,12 +36,26 @@ void draw_contact_form(contact* buffer, bool viewing_only = false)
// 2. Full name
ImGui::SetNextItemWidth(widthAvailable*0.5f);
if (with_autocomplete) {
- contact autocomplete_list[5];
- int autocomplete_count = 5;
- char* autocomplete_strings[5] = { "1", "2", "3", "4", "5" };
+ contact autocomplete_list[5];
+ int autocomplete_count = administration_get_contact_recommendations(autocomplete_list, 5, buffer->name);
+ char* autocomplete_strings[5];
+
+ for (int i = 0; i < autocomplete_count; i++)
+ {
+ autocomplete_strings[i] = autocomplete_list[i].name;
+ }
- ui_helper_TextInputWithAutocomplete(localize("contact.form.fullname"), localize("contact.form.fullname"),
+ int autocomplete_index = ui_helper_TextInputWithAutocomplete(localize("contact.form.fullname"), localize("contact.form.fullname"),
buffer->name, IM_ARRAYSIZE(buffer->name), (char**)autocomplete_strings, autocomplete_count);
+
+ if (on_autocomplete) {
+ *on_autocomplete = autocomplete_index != -1;
+ }
+
+ if (autocomplete_index != -1)
+ {
+ memcpy(buffer, &autocomplete_list[autocomplete_index], sizeof(contact));
+ }
}
else ImGui::InputTextWithHint(localize("contact.form.fullname"), localize("contact.form.fullname"), buffer->name, IM_ARRAYSIZE(buffer->name));
ImGui::SameLine();ui_helper_draw_required_tag();
@@ -131,6 +144,12 @@ void draw_contact_form(contact* buffer, bool viewing_only = false)
if (viewing_only) ImGui::EndDisabled();
}
+void draw_contact_form(contact* buffer, bool viewing_only = false)
+{
+ draw_contact_form_ex(buffer, viewing_only, false, 0);
+}
+
+
static void draw_contact_list()
{
const u32 items_per_page = 50;
diff --git a/src/ui/ui_invoices.cpp b/src/ui/ui_invoices.cpp
index fe4f5a8..8ee099c 100644
--- a/src/ui/ui_invoices.cpp
+++ b/src/ui/ui_invoices.cpp
@@ -1,4 +1,7 @@
#include <stdio.h>
+#include <time.h>
+
+#include "ImGuiDatePicker/ImGuiDatePicker.hpp"
#include "strops.hpp"
#include "ui.hpp"
@@ -9,7 +12,7 @@
static view_state current_view_state = view_state::LIST;
static invoice active_invoice;
-extern void draw_contact_form(contact* buffer, bool viewing_only = false);
+void draw_contact_form_ex(contact* buffer, bool viewing_only = false, bool with_autocomplete = false, bool* on_autocomplete = 0);
void ui_setup_invoices()
{
@@ -17,23 +20,16 @@ void ui_setup_invoices()
active_invoice = administration_create_empty_invoice();
}
-bool draw_invoice_form(invoice* buffer, bool back_button_enabled = true, bool viewing_only = false)
+void draw_invoice_form(invoice* buffer, bool viewing_only = false)
{
- if (back_button_enabled)
- {
- if (ImGui::Button(localize("form.back"))) {
- current_view_state = view_state::LIST;
- return false;
- }
- }
- ImGui::Spacing();
- float widthAvailable = ImGui::GetContentRegionAvail().x;
+ //float widthAvailable = ImGui::GetContentRegionAvail().x;
+ ImGui::BeginDisabled();
// 1. Identifier
- //ImGui::BeginDisabled();
//ImGui::SetNextItemWidth(widthAvailable*0.2f);
//ImGui::InputText(localize("contact.form.identifier"), buffer->id, IM_ARRAYSIZE(buffer->id));
-
+ if (!viewing_only) ImGui::EndDisabled();
+
// 2. Sequential number
ImGui::Text("Invoice number: %s", buffer->sequential_number);
@@ -42,20 +38,54 @@ bool draw_invoice_form(invoice* buffer, bool back_button_enabled = true, bool vi
ImGui::Separator();
+ // 4. Customer information
ImGui::Text("Customer information");
- draw_contact_form(&buffer->customer);
- strops_copy(buffer->customer_id, buffer->customer.id, sizeof(buffer->customer_id));
+ bool on_autocomplete;
+ draw_contact_form_ex(&buffer->customer, false, true, &on_autocomplete);
+
+ if (on_autocomplete) {
+ strops_copy(buffer->customer_id, buffer->customer.id, sizeof(buffer->customer_id));
+ }
ImGui::Separator();
+ // 5. Invoice issued at
+ ImGui::BeginDisabled();
+ tm issued_at_date = *gmtime(&buffer->issued_at);
+ if (ImGui::DatePicker("##issuedAt", issued_at_date))
+ {
+ buffer->issued_at = mktime(&issued_at_date);
+ }
+ ImGui::SameLine();
+ ImGui::Text("Invoice issued at");
+ ImGui::EndDisabled();
+
+ // 6. Invoice expires at
+ ImGui::BeginDisabled();
+ tm expires_at_date = *gmtime(&buffer->expires_at);
+ if (ImGui::DatePicker("##expiresAt", expires_at_date))
+ {
+ buffer->expires_at = mktime(&expires_at_date);
+ }
+ ImGui::SameLine();
+ ImGui::Text("Invoice expires at");
+ ImGui::EndDisabled();
+
+ // 7. Product/service delivered at
+ tm delivered_at_date = *gmtime(&buffer->delivered_at);
+ if (ImGui::DatePicker("##deliveredAt", delivered_at_date))
+ {
+ buffer->delivered_at = mktime(&delivered_at_date);
+ }
+ ImGui::SameLine();
+ ImGui::Text("Product/service delivered at");
+
//ImGui::SetNextItemWidth(widthAvailable*0.5f);
//ImGui::InputTextWithHint("Invoice number", "Invoice number", buffer->sequential_number, IM_ARRAYSIZE(buffer->sequential_number));
//ImGui::SameLine();ui_helper_draw_required_tag();
- //if (!viewing_only) ImGui::EndDisabled();
-
- return false;
+ if (viewing_only) ImGui::EndDisabled();
}
void draw_invoices_list()
@@ -75,12 +105,17 @@ void ui_draw_invoices()
switch(current_view_state)
{
case view_state::LIST: draw_invoices_list(); break;
- case view_state::CREATE:
- if (draw_invoice_form(&active_invoice))
- {
- //administration_create_invoice(active_invoice);
+ case view_state::CREATE:
+
+ if (ImGui::Button(localize("form.back"))) {
current_view_state = view_state::LIST;
- }
+ }
+ draw_invoice_form(&active_invoice);
+
+ //if () {
+ //administration_create_invoice(active_invoice);
+ //current_view_state = view_state::LIST;
+ //}
break;
case view_state::EDIT: break;
case view_state::VIEW: break;
diff --git a/src/ui/ui_projects.cpp b/src/ui/ui_projects.cpp
index 5cb412f..3c550a2 100644
--- a/src/ui/ui_projects.cpp
+++ b/src/ui/ui_projects.cpp
@@ -45,7 +45,7 @@ static void draw_project_form()
if (viewing_only) ImGui::EndDisabled();
if (!viewing_only) {
- bool can_save = strlen(active_project.description) > 0;
+ bool can_save = administration_is_project_valid(active_project);
if (!can_save) ImGui::BeginDisabled();
// Save button