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_templates.c | |
| parent | 3a5a6d3b04dc4e17a42225afb243eecb3502ead6 (diff) | |
| download | bokf-10a535ace098144980d0ffd1593f6384c39cc221.tar.gz bokf-10a535ace098144980d0ffd1593f6384c39cc221.zip | |
tui: split bokftui into screen files
Diffstat (limited to 'clients/screens_templates.c')
| -rw-r--r-- | clients/screens_templates.c | 343 |
1 files changed, 343 insertions, 0 deletions
diff --git a/clients/screens_templates.c b/clients/screens_templates.c new file mode 100644 index 0000000..b2cd7ab --- /dev/null +++ b/clients/screens_templates.c @@ -0,0 +1,343 @@ +#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" + +struct tui_trow { + char account[16]; + char formula[64]; + char text[64]; +}; + +struct tplform { + struct app *a; + struct tui_rt rt; + struct tui_trow rows[64]; + int nrows; + char name[128]; + char desc[256]; + char series[16]; + int64_t id; +}; + +static void tpl_cell(void *ud, int row, int col, char **buf, size_t *cap) +{ + struct tplform *tf = ud; + struct tui_trow *r = &tf->rows[row]; + if (col == 0) { + *buf = r->account; + *cap = sizeof r->account; + } else if (col == 2) { + *buf = r->formula; + *cap = sizeof r->formula; + } else { + *buf = r->text; + *cap = sizeof r->text; + } +} + +static void tpl_info(void *ud, int row, char *buf, size_t n) +{ + struct tplform *tf = ud; + const char *nm = acct_name(tf->a, tf->rows[row].account); + snprintf(buf, n, "%s", nm ? nm : ""); +} + +static void tpl_footer(void *ud, char *buf, size_t n) +{ + (void)ud; + snprintf(buf, n, "x = belopp i kronor. Positivt blir debet, negativt" + " kredit."); +} + +/* F5 dry-runs; Ctrl+Enter creates/updates. Returns 1 when saved. */ +static int tpl_save(struct tplform *tf, int dry) +{ + const char *problem = NULL; + char msg[256] = ""; + if (!tf->name[0]) + problem = "Namn saknas."; + int used = 0; + for (int i = 0; i < tf->rt.nrows && !problem; i++) { + if (!tf->rows[i].account[0]) + continue; + if (!acct_exists(tf->a, tf->rows[i].account)) { + snprintf(msg, sizeof msg, "Konto %.15s finns inte (rad %d).", + tf->rows[i].account, i + 1); + problem = msg; + break; + } + if (!tf->rows[i].formula[0] || !formula_valid(tf->rows[i].formula)) { + snprintf(msg, sizeof msg, + "Ogiltig formel på rad %d: '%.60s'.", i + 1, + tf->rows[i].formula); + problem = msg; + break; + } + used++; + } + if (!problem && used == 0) + problem = "Mallen behöver minst en rad."; + if (problem) { + tui_message("Mall", "%s", problem); + return 0; + } + + yyjson_mut_doc *d = yyjson_mut_doc_new(NULL); + yyjson_mut_val *o = yyjson_mut_obj(d); + yyjson_mut_doc_set_root(d, o); + if (tf->id) + yyjson_mut_obj_add_int(d, o, "id", tf->id); + yyjson_mut_obj_add_strcpy(d, o, "name", tf->name); + yyjson_mut_obj_add_strcpy(d, o, "series", tf->series); + yyjson_mut_obj_add_strcpy(d, o, "description", tf->desc); + yyjson_mut_val *arr = yyjson_mut_arr(d); + for (int i = 0; i < tf->rt.nrows; i++) { + if (!tf->rows[i].account[0]) + continue; + yyjson_mut_val *ro = yyjson_mut_arr_add_obj(d, arr); + yyjson_mut_obj_add_strcpy(d, ro, "account", tf->rows[i].account); + yyjson_mut_obj_add_strcpy(d, ro, "formula", tf->rows[i].formula); + if (tf->rows[i].text[0]) + yyjson_mut_obj_add_strcpy(d, ro, "description", + tf->rows[i].text); + } + yyjson_mut_obj_add_val(d, o, "rows", arr); + char *args = yyjson_mut_write(d, 0, NULL); + yyjson_mut_doc_free(d); + const char *cmd = tf->id ? "template.update" : "template.create"; + char *resp = dry ? rpc_dry(tf->a, cmd, args) + : client_rpc(&tf->a->conn, cmd, tf->a->session, + tf->a->org, args); + free(args); + if (resp && client_ok(resp)) { + if (dry) { + tui_message("Validering OK", "Mallen är korrekt (%d rader).", + used); + } else { + tui_message("Mall", "Mallen '%s' sparad.", tf->name); + free(resp); + return 1; + } + } else { + show_error("Kunde inte spara mallen", resp); + } + free(resp); + return 0; +} + +/* Template editor: a form, not a wizard. load_name == NULL creates a new + template, otherwise the named template is loaded and saved via + template.update. */ +static int template_form(struct app *a, const char *load_name) +{ + struct tplform tf; + memset(&tf, 0, sizeof tf); + tf.a = a; + snprintf(tf.series, sizeof tf.series, "%s", a->default_series); + if (load_name && *load_name) { + 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, "name", load_name); + char *args = yyjson_mut_write(d, 0, NULL); + yyjson_mut_doc_free(d); + char *resp = + client_rpc(&a->conn, "template.get", a->session, a->org, args); + free(args); + if (!resp || !client_ok(resp)) { + show_error("Mall", resp); + free(resp); + return 0; + } + tf.id = jint_val(resp, "result.id", 0); + char *nm = jstr_dup(resp, "result.name"); + char *ser = jstr_dup(resp, "result.series"); + char *ds = jstr_dup(resp, "result.description"); + if (nm) + snprintf(tf.name, sizeof tf.name, "%s", nm); + if (ser && *ser) + snprintf(tf.series, sizeof tf.series, "%s", ser); + if (ds) + snprintf(tf.desc, sizeof tf.desc, "%s", ds); + free(nm); + free(ser); + free(ds); + size_t n = jarr_size(resp, "result.rows"); + if (n > 64) + n = 64; + for (size_t i = 0; i < n; i++) { + char path[64]; + snprintf(path, sizeof path, "result.rows.%zu.account", i); + char *acc = jstr_dup(resp, path); + snprintf(path, sizeof path, "result.rows.%zu.formula", i); + char *form = jstr_dup(resp, path); + snprintf(path, sizeof path, "result.rows.%zu.description", i); + char *rd = jstr_dup(resp, path); + snprintf(tf.rows[i].account, sizeof tf.rows[i].account, "%s", + acc ? acc : ""); + snprintf(tf.rows[i].formula, sizeof tf.rows[i].formula, "%s", + form ? form : ""); + snprintf(tf.rows[i].text, sizeof tf.rows[i].text, "%s", + rd ? rd : ""); + free(acc); + free(form); + free(rd); + } + tf.nrows = n > 0 ? (int)n : 1; + free(resp); + } + int text_w = COLS - 57; + if (text_w < 8) + text_w = 8; + struct tui_rt_col cols[4] = { + { "Konto", 2, 6, TUI_RT_EDIT }, + { "Namn", 9, 24, TUI_RT_INFO }, + { "Formel", 34, 20, TUI_RT_EDIT }, + { "Radtext", 55, text_w, TUI_RT_EDIT }, + }; + tui_rt_init(&tf.rt, tf.nrows > 0 ? tf.nrows : 1, 64, 4, cols, 7, tpl_cell, + tpl_info, &tf); + tui_rt_set_footer(&tf.rt, tpl_footer); + struct tui_form_field ff[3]; + memset(ff, 0, sizeof ff); + ff[0].label = "Namn"; + ff[0].value = tf.name; + ff[0].cap = sizeof tf.name; + ff[0].kind = TUI_F_TEXT; + ff[1].label = "Serie"; + ff[1].value = tf.series; + ff[1].cap = sizeof tf.series; + ff[1].kind = TUI_F_TEXT; + ff[2].label = "Text"; + ff[2].value = tf.desc; + ff[2].cap = sizeof tf.desc; + ff[2].kind = TUI_F_TEXT; + tui_rt_set_fields(&tf.rt, ff, 3); + const char *title = load_name ? "Redigera mall" : "Ny mall"; + const char *hint = "Enter = ändra fält Tab = byta fält F5 = validera" + " ^X = rensa rad ^Enter = spara Esc = avbryt"; + for (;;) { + int rr = tui_rt_run(title, &tf.rt, hint); + if (rr == -1) + return 0; + if (rr == -2) { + tpl_save(&tf, 1); + continue; + } + if (rr == -3 && tpl_save(&tf, 0)) + return 1; + } +} + +static void template_archive(struct app *a, const char *name) +{ + char label[256]; + snprintf(label, sizeof label, "Arkivera '%s'? (j/n): ", name); + char ansbuf[512]; char *ans = tui_prompt_into(ansbuf, sizeof ansbuf, label, "n", 0) ? ansbuf : NULL; + if (!ans || (ans[0] != 'j' && ans[0] != 'J')) + return; + 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, "name", name); + char *args = yyjson_mut_write(d, 0, NULL); + yyjson_mut_doc_free(d); + char *resp = + client_rpc(&a->conn, "template.archive", a->session, a->org, args); + free(args); + if (resp && client_ok(resp)) + tui_message("Mall", "Mallen '%s' arkiverad.", name); + else + show_error("Kunde inte arkivera", resp); + free(resp); +} + +void templates_screen(struct app *a) +{ + int cursor = 0; + for (;;) { + if (g_quit) + return; + char *resp = client_rpc(&a->conn, "template.list", a->session, a->org, + "{\"active_only\":true}"); + if (!resp || !client_ok(resp)) { + show_error("Mallar", resp); + free(resp); + return; + } + size_t n = jarr_size(resp, "result.items"); + char **names = xcalloc(n + 1, sizeof(char *)); + char **lines = xcalloc(n + 1, sizeof(char *)); + for (size_t i = 0; i < n; i++) { + char path[64], line[512]; + snprintf(path, sizeof path, "result.items.%zu.name", i); + char *nm = jstr_dup(resp, path); + snprintf(path, sizeof path, "result.items.%zu.series", i); + char *ser = jstr_dup(resp, path); + snprintf(path, sizeof path, "result.items.%zu.row_count", i); + int64_t rc = jint_val(resp, path, 0); + snprintf(path, sizeof path, "result.items.%zu.description", i); + char *ds = jstr_dup(resp, path); + char nmbuf[128]; + snprintf(nmbuf, sizeof nmbuf, "%s", nm ? nm : ""); + tui_pad_field(nmbuf, sizeof nmbuf, 24); + snprintf(line, sizeof line, "%s %-3s %2lld rader %s", nmbuf, + ser ? ser : "", (long long)rc, ds ? ds : ""); + names[i] = xstrdup(nm ? nm : ""); + lines[i] = xstrdup(line); + free(nm); + free(ser); + free(ds); + } + names[n] = xstrdup(""); + lines[n] = xstrdup("+ Ny mall (^N)"); + int s = tui_select_list("Mallar", lines, (int)n + 1, cursor, 1, &cursor, 1, + n > 0 ? "arkivera" : NULL, 0); + if (s == -1) { + for (size_t i = 0; i <= n; i++) { + free(names[i]); + free(lines[i]); + } + free(names); + free(lines); + free(resp); + return; + } + if (s == -6) { + if (cursor >= 0 && (size_t)cursor < n) + template_archive(a, names[cursor]); + } else if (s == -4 || (s >= 0 && (size_t)s == n)) { + template_form(a, NULL); + } else if (s >= 0) { + template_form(a, names[s]); + } + for (size_t i = 0; i <= n; i++) { + free(names[i]); + free(lines[i]); + } + free(names); + free(lines); + free(resp); + if (s == -5) + return; + } +} |
