diff options
| author | Anders Betts <anders.betts@gmail.com> | 2026-09-20 22:00:01 +0200 |
|---|---|---|
| committer | Anders Betts <anders.betts@gmail.com> | 2026-09-20 22:00:01 +0200 |
| commit | 10a535ace098144980d0ffd1593f6384c39cc221 (patch) | |
| tree | 53a7c6c5026d479c4b6d5fd4e5b341474cd190fa /clients/screens_dashboard.c | |
| parent | 3a5a6d3b04dc4e17a42225afb243eecb3502ead6 (diff) | |
| download | bokf-10a535ace098144980d0ffd1593f6384c39cc221.tar.gz bokf-10a535ace098144980d0ffd1593f6384c39cc221.zip | |
tui: split bokftui into screen files
Diffstat (limited to 'clients/screens_dashboard.c')
| -rw-r--r-- | clients/screens_dashboard.c | 354 |
1 files changed, 354 insertions, 0 deletions
diff --git a/clients/screens_dashboard.c b/clients/screens_dashboard.c new file mode 100644 index 0000000..0c133b2 --- /dev/null +++ b/clients/screens_dashboard.c @@ -0,0 +1,354 @@ +#include <dirent.h> +#include <errno.h> +#include <locale.h> +#include <math.h> +#include <ncursesw/ncurses.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <time.h> +#include <unistd.h> + +#include "client.h" +#include "tui.h" +#include "formula.h" +#include "util.h" +#include "version.h" +#include "yyjson.h" +#include "ui.h" + +void update_status(struct app *a) +{ + char st[512], right[160]; + snprintf(st, sizeof st, "%s | %s - %s | %s | %s", a->org_name, + a->fy_start, a->fy_end, a->role, a->username); + if (g_server_version[0]) + snprintf(right, sizeof right, "tui %s srv %s", BOKF_VERSION, + g_server_version); + else + snprintf(right, sizeof right, "tui %s", BOKF_VERSION); + tui_set_status(st, right); +} + +/* Create a new fiscal year. Returns the new id (>0) or 0. */ +static int64_t fy_new_form(struct app *a) +{ + static int sel = 0; + char label[32] = "", start[16] = "", end[16] = ""; + char *resp = + client_rpc(&a->conn, "fiscal_year.list", a->session, a->org, "{}"); + if (resp && client_ok(resp)) { + size_t n = jarr_size(resp, "result.items"); + char max_end[16] = ""; + for (size_t i = 0; i < n; i++) { + char path[64]; + snprintf(path, sizeof path, "result.items.%zu.end_date", i); + char *e = jstr_dup(resp, path); + if (e && strcmp(e, max_end) > 0) + snprintf(max_end, sizeof max_end, "%s", e); + free(e); + } + if (max_end[0]) + util_date_add_days(max_end, 1, start, sizeof start); + } + free(resp); + if (!start[0]) { + time_t t = time(NULL); + struct tm tm; + gmtime_r(&t, &tm); + int y = tm.tm_year + 1900; + if (y < 1900 || y > 2200) + y = 2026; + snprintf(start, sizeof start, "%04d-01-01", y); + } + util_date_add_months(start, 12, end, sizeof end); + util_date_add_days(end, -1, end, sizeof end); + int sy = 0, sm = 0, sd = 0, ey = 0, em = 0, ed = 0; + util_date_parse(start, &sy, &sm, &sd); + util_date_parse(end, &ey, &em, &ed); + if (sm == 1 && sd == 1) + snprintf(label, sizeof label, "%d", ey); + else + snprintf(label, sizeof label, "%d/%d", sy, ey); + + struct tui_form_field ff[3]; + for (;;) { + memset(ff, 0, sizeof ff); + ff[0].label = "Etikett"; + ff[0].value = label; + ff[0].cap = sizeof label; + ff[0].kind = TUI_F_TEXT; + ff[1].label = "Startdatum"; + ff[1].value = start; + ff[1].cap = sizeof start; + ff[1].kind = TUI_F_DATE; + ff[2].label = "Slutdatum"; + ff[2].value = end; + ff[2].cap = sizeof end; + ff[2].kind = TUI_F_DATE; + int r = tui_form_run("Nytt räkenskapsår", ff, 3, 1, &sel); + if (r == TUI_FORM_BACK) + return 0; + if (r != TUI_FORM_REFRESH && r != TUI_FORM_SUBMIT) + continue; /* a field was edited; keep the form up */ + if (!label[0]) { + tui_message("Räkenskapsår", "Etiketten får inte vara tom."); + continue; + } + if (!util_parse_iso_date(start) || !util_parse_iso_date(end)) { + tui_message("Räkenskapsår", + "Start- och slutdatum måste vara ÅÅÅÅ-MM-DD."); + continue; + } + if (strcmp(start, end) >= 0) { + tui_message("Räkenskapsår", + "Startdatum måste ligga före slutdatum."); + continue; + } + if (r == TUI_FORM_REFRESH) { + tui_message("Validering OK", "%s %s - %s", label, start, end); + continue; + } + yyjson_mut_doc *d = yyjson_mut_doc_new(NULL); + yyjson_mut_val *o = yyjson_mut_obj(d); + yyjson_mut_doc_set_root(d, o); + yyjson_mut_obj_add_strcpy(d, o, "label", label); + yyjson_mut_obj_add_strcpy(d, o, "start_date", start); + yyjson_mut_obj_add_strcpy(d, o, "end_date", end); + char *args = yyjson_mut_write(d, 0, NULL); + yyjson_mut_doc_free(d); + char *oresp = client_rpc(&a->conn, "fiscal_year.open", a->session, + a->org, args); + free(args); + if (oresp && client_ok(oresp)) { + int64_t id = jint_val(oresp, "result.id", 0); + tui_message("Räkenskapsår", "%s skapat.", label); + free(oresp); + return id; + } + show_error("Kunde inte skapa räkenskapsåret", oresp); + free(oresp); + } +} + +/* Pick which fiscal year the screens work in. Shown as its real range so + broken fiscal years (not starting in January) are obvious. Ctrl+A closes + or reopens the highlighted year. */ +static void select_fiscal_year(struct app *a) +{ + int64_t prefer = a->fy; + int cursor = -1; + for (;;) { + if (g_quit) + return; + char *resp = + client_rpc(&a->conn, "fiscal_year.list", a->session, a->org, "{}"); + if (!resp || !client_ok(resp)) { + show_error("Räkenskapsår", resp); + free(resp); + return; + } + size_t n = jarr_size(resp, "result.items"); + if (n == 0) { + free(resp); + int64_t id = fy_new_form(a); + if (id > 0) + prefer = id; + continue; + } + char **lines = xcalloc(n, sizeof(char *)); + int64_t *ids = xcalloc(n, sizeof(int64_t)); + int *closed = xcalloc(n, sizeof(int)); + char (*ranges)[40] = xcalloc(n, sizeof *ranges); + char (*labels)[64] = xcalloc(n, sizeof *labels); + int sel_default = 0; + for (size_t i = 0; i < n; i++) { + char path[64]; + snprintf(path, sizeof path, "result.items.%zu.id", i); + ids[i] = jint_val(resp, path, 0); + snprintf(path, sizeof path, "result.items.%zu.label", i); + char *label = jstr_dup(resp, path); + snprintf(path, sizeof path, "result.items.%zu.start_date", i); + char *sdate = jstr_dup(resp, path); + snprintf(path, sizeof path, "result.items.%zu.end_date", i); + char *edate = jstr_dup(resp, path); + snprintf(path, sizeof path, "result.items.%zu.status", i); + char *status = jstr_dup(resp, path); + snprintf(ranges[i], sizeof ranges[i], "%s - %s", + sdate ? sdate : "?", edate ? edate : "?"); + snprintf(labels[i], sizeof labels[i], "%s", label ? label : ""); + closed[i] = strcmp(status ? status : "", "closed") == 0; + char line[256], stbuf[16]; + snprintf(stbuf, sizeof stbuf, "%s", closed[i] ? "stängd" : "öppen"); + tui_pad_field(stbuf, sizeof stbuf, 6); + snprintf(line, sizeof line, "%-25s %s %s", ranges[i], stbuf, + labels[i]); + lines[i] = xstrdup(line); + if (ids[i] == prefer) + sel_default = (int)i; + free(label); + free(sdate); + free(edate); + free(status); + } + int start = cursor >= 0 ? cursor : sel_default; + if (start >= (int)n) + start = (int)n - 1; + int sel = tui_select_list("Räkenskapsår", lines, (int)n, start, 0, + &cursor, 1, NULL, 1); + for (size_t i = 0; i < n; i++) + free(lines[i]); + free(lines); + free(resp); + if (sel == -4) { + free(ids); + free(closed); + free(ranges); + free(labels); + int64_t id = fy_new_form(a); + if (id > 0) + prefer = id; + continue; + } + if (sel == -7) { + if (cursor >= 0 && (size_t)cursor < n) { + char label[192]; + snprintf(label, sizeof label, "%s räkenskapsåret %s? (j/n): ", + closed[cursor] ? "Återöppna" : "Avsluta", + labels[cursor]); + char ansbuf[512]; char *ans = tui_prompt_into(ansbuf, sizeof ansbuf, label, "n", 0) ? ansbuf : NULL; + if (ans && (ans[0] == 'j' || ans[0] == 'J')) { + char args[64]; + snprintf(args, sizeof args, + "{\"id\":%lld,\"confirm\":true}", + (long long)ids[cursor]); + char *r = client_rpc(&a->conn, + closed[cursor] + ? "fiscal_year.reopen" + : "fiscal_year.close", + a->session, a->org, args); + if (r && client_ok(r)) + tui_message("Räkenskapsår", "Räkenskapsåret %s är %s.", + labels[cursor], + closed[cursor] ? "återöppnat" : "avslutat"); + else + show_error("Kunde inte ändra status", r); + free(r); + } + } + free(ids); + free(closed); + free(ranges); + free(labels); + continue; + } + if (sel < 0) { + free(ids); + free(closed); + free(ranges); + free(labels); + return; + } + a->fy = ids[sel]; + snprintf(a->fy_label, sizeof a->fy_label, "%s", labels[sel]); + /* range is "start - end" */ + snprintf(a->fy_start, sizeof a->fy_start, "%.10s", ranges[sel]); + snprintf(a->fy_end, sizeof a->fy_end, "%s", ranges[sel] + 13); + free(ids); + free(closed); + free(ranges); + free(labels); + a->voucher_sel = 0; + update_status(a); + return; + } +} +static const char *const MAIN_ITEMS[] = { + MK_SECTION "Bokföring", + "Verifikat", "Underlag (inkorg)", "Bankavstämning", + MK_SECTION "Fakturering", + "Fakturor", + MK_SECTION "Rapporter & bokslut", + "Rapporter", "Bokslut", + MK_SECTION "Register", + "Mallar", "Kunder", "Momsregler", "Företagsuppgifter", "Inställningar", + MK_SECTION "Räkenskapsår", + "Ingående balans", "Räkenskapsår", + MK_SECTION "Övrigt", + "Revision", "Logga ut / avsluta", +}; + +void dashboard(struct app *a) +{ + static int last_sel = 0; + for (;;) { + if (g_reload) + return; + int sel = tui_menu("Bokf", MAIN_ITEMS, + (int)(sizeof MAIN_ITEMS / sizeof MAIN_ITEMS[0]), 1, + &last_sel); + snprintf(g_scene, sizeof g_scene, "%s", "dashboard"); + if (g_quit || g_reload) + return; + if (sel == -4) { + vouchers_new(a); + continue; + } + if (sel == -5) + return; + if (sel < 0) + continue; /* Esc/back at the top level never exits */ + switch (sel) { + case 1: + open_scene(a, "vouchers"); + break; + case 2: + open_scene(a, "inbox"); + break; + case 3: + open_scene(a, "bank"); + break; + case 5: + open_scene(a, "invoices"); + break; + case 7: + open_scene(a, "reports"); + break; + case 8: + open_scene(a, "bokslut"); + break; + case 10: + open_scene(a, "templates"); + break; + case 11: + open_scene(a, "customers"); + break; + case 12: + open_scene(a, "rules"); + break; + case 13: + open_scene(a, "company"); + break; + case 14: + open_scene(a, "settings"); + break; + case 16: + open_scene(a, "ib"); + break; + case 17: + select_fiscal_year(a); + break; + case 19: + open_scene(a, "audit"); + break; + case 20: + return; + default: + break; /* section header */ + } + } +} |
