timekeeper

[Abandoned unfinished] CGI web application in C for time tracking. (My first, just a learning project)
Log | Files | Refs | README

commit 7f4a3304dea370c99688139566d40e7929201b9f
parent 2437e14af87e64ce4fb832d5c75e23a44446bbf7
Author: Jacob R. Edwards <jacob@jacobedwards.org>
Date:   Mon, 11 Mar 2024 19:35:26 -0700

Add total row

The writing of this made me realize that there isn't any way of
telling an error from a zero-length return in gettimes(), so that's
fixed now too.

Diffstat:
Mpages/export.c | 6++++--
Mpages/main.c | 105++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Mscripts/counter.js | 31+++++++++++++++++++------------
Mtimes.c | 9+++++----
Mtimes.h | 2+-
5 files changed, 101 insertions(+), 52 deletions(-)

diff --git a/pages/export.c b/pages/export.c @@ -46,8 +46,7 @@ pageexport(struct pagedata *pd) serialize = serialize_epoch; } - times = gettimes(pd, pd->user->hash); - if (!times) + if (gettimes(pd, pd->user->hash, &times)) return errorpage(pd, KHTTP_500); if ((status = khttp_head(&pd->req, kresps[KRESP_STATUS], @@ -61,6 +60,9 @@ pageexport(struct pagedata *pd) return status; } + if (!times) + return KCGI_OK; + for (ts = times; ts; ts = ts->next) { for (n = 0; n < 4; ++n) { if (n && (status = khttp_putc(&pd->req, '\t')) != KCGI_OK) { diff --git a/pages/main.c b/pages/main.c @@ -25,36 +25,24 @@ static char *datetime_fmt = "%FT%T+0000"; enum kcgi_err -printhours(struct pagedata *pd, struct timesheet *ts) +printduration(struct pagedata *pd, time_t duration, int final) { enum kcgi_err status; - time_t elapsed; time_t hr, mn, sc; - struct tm *tm; char datetime[13]; /* allows up to four digits for hours */ - int final; - elapsed = getduration(ts); - assert(elapsed >= 0); + assert(duration >= 0); - mn = elapsed / 60; - sc = elapsed % 60; + mn = duration / 60; + sc = duration % 60; hr = mn / 60; mn = mn % 60; - assert(ts->set & StartTimeFlag); - tm = gmtime(&ts->times[0]); - if (!tm) - err(1, "gmtime"); - if (snprintf(datetime, sizeof(datetime), "%lldh%lldm%llds", hr, mn, sc) >= (int)sizeof(datetime)) { err(1, "snprintf (%lldh %lldm %llds)", hr, mn, sc); } - final = (ts->set & EndTimeFlag || - (ts->set & BreakStartTimeFlag && !(ts->set & BreakEndTimeFlag))); - if (!final) status = khtml_attr(&pd->html, KELEM_TIME, KATTR_ID, "counter", KATTR_DATETIME, datetime, KATTR__MAX); @@ -70,10 +58,7 @@ printhours(struct pagedata *pd, struct timesheet *ts) return status; if (!final) { - if ((status = khtml_attr(&pd->html, KELEM_SCRIPT, - KATTR_SRC, "scripts/counter.js", KATTR__MAX)) != KCGI_OK || - (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK || - (status = khtml_elem(&pd->html, KELEM_NOSCRIPT)) != KCGI_OK || + if ((status = khtml_elem(&pd->html, KELEM_NOSCRIPT)) != KCGI_OK || (status = khtml_attr(&pd->html, KELEM_A, KATTR_HREF, pd->pages[PageMain], KATTR__MAX)) != KCGI_OK || (status = khtml_putc(&pd->html, ' ')) != KCGI_OK || @@ -86,6 +71,44 @@ printhours(struct pagedata *pd, struct timesheet *ts) } enum kcgi_err +printhours(struct pagedata *pd, struct timesheet *ts) +{ + int final; + + assert(ts->set & StartTimeFlag); + + final = (ts->set & EndTimeFlag || + (ts->set & BreakStartTimeFlag && !(ts->set & BreakEndTimeFlag))); + return printduration(pd, getduration(ts), final); +} + +enum kcgi_err +printtotal(struct pagedata *pd, struct timesheet *ts) +{ + enum kcgi_err status; + time_t t; + int i; + + t = 0; + for (; ts; ts = ts->next) + if (ts->set & StartTimeFlag) + t += getduration(ts); + + if ((status = khtml_attr(&pd->html, + KELEM_TH, KATTR_SCOPE, "row", KATTR__MAX)) != KCGI_OK || + (status = khtml_puts(&pd->html, "Total")) != KCGI_OK || + (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK) + return status; + + for (i = StartTime; i <= EndTime + 1; ++i) + if ((status = khtml_elem(&pd->html, KELEM_TD)) != KCGI_OK || + (i > EndTime && (status = printduration(pd, t, 1)) != KCGI_OK) || + (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK) + return status; + return KCGI_OK; +} + +enum kcgi_err printtimebutton(struct pagedata *pd, enum time_field tf) { enum kcgi_err status; @@ -153,10 +176,9 @@ printtime(struct pagedata *pd, struct timesheet *ts) timesheet_set(ts, BreakEndTime, ts->times[EndTime]); for (i = 0; i < 4; ++i) { + if ((status = khtml_elem(&pd->html, KELEM_TD)) != KCGI_OK) + return status; if (!(ts->set & timeflagmap[i])) { - if ((status = khtml_elem(&pd->html, KELEM_TD)) != KCGI_OK) - return status; - /* This does not validate input */ if (i == StartTime) status = printtimebutton(pd, StartTime); @@ -169,8 +191,7 @@ printtime(struct pagedata *pd, struct timesheet *ts) status = printtimebutton(pd, i); else status = khtml_putc(&pd->html, '-'); - - if (status != KCGI_OK || (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK) + if (status != KCGI_OK) return status; } else { if (i) { @@ -183,13 +204,14 @@ printtime(struct pagedata *pd, struct timesheet *ts) strftime(time, sizeof(time), "%R", tm) >= sizeof(time)) return KCGI_SYSTEM; - if ((status = khtml_elem(&pd->html, KELEM_TD)) != KCGI_OK || - (status = khtml_attr(&pd->html, KELEM_TIME, + if ((status = khtml_attr(&pd->html, KELEM_TIME, KATTR_DATETIME, datetime, KATTR__MAX)) != KCGI_OK || (status = khtml_puts(&pd->html, time)) != KCGI_OK || - (status = khtml_closeelem(&pd->html, 2)) != KCGI_OK) + (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK) return status; } + if ((status = khtml_closeelem(&pd->html, 1)) != KCGI_OK) + return status; } if ((status = khtml_elem(&pd->html, KELEM_TD)) != KCGI_OK || @@ -207,6 +229,7 @@ printtimes(struct pagedata *pd, struct timesheet *times) char *headers[] = { "Date", "Start time", "Break start", "Break end", "End time", "Hours" }; + struct timesheet *p; if ((status = khtml_elem(&pd->html, KELEM_TABLE)) != KCGI_OK || (status = htmlwithin(pd, KELEM_CAPTION, "Timesheets")) != KCGI_OK) @@ -228,15 +251,27 @@ printtimes(struct pagedata *pd, struct timesheet *times) (status = khtml_elem(&pd->html, KELEM_TBODY)) != KCGI_OK) return status; - for (; times->next; times = times->next) + for (p = times; p->next; p = p->next) ; - for (; times; times = times->prev) { + for (; p; p = p->prev) { if ((status = khtml_elem(&pd->html, KELEM_TR)) != KCGI_OK || - (status = printtime(pd, times)) != KCGI_OK || + (status = printtime(pd, p)) != KCGI_OK || (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK) return status; } + if ((status = khtml_closeelem(&pd->html, 1)) != KCGI_OK) + return status; + + if (times->set & EndTimeFlag && + (times->next && times->next->set & EndTimeFlag)) { + if ((status = khtml_elem(&pd->html, KELEM_TFOOT)) != KCGI_OK || + (status = khtml_elem(&pd->html, KELEM_TR)) != KCGI_OK || + (status = printtotal(pd, times)) != KCGI_OK || + (status = khtml_closeelem(&pd->html, 2)) != KCGI_OK) + return status; + } + return khtml_closeelem(&pd->html, 2); } @@ -276,8 +311,9 @@ pagemain(struct pagedata *pd) if (status != KCGI_OK) return status; - times = gettimes(pd, pd->user->hash); - if (!times) + if (gettimes(pd, pd->user->hash, &times)) + err(1, "gettimes"); + else if (!times && !(times = newtimesheet())) err(1, "gettimes"); for (end = times; end->next; end = end->next) @@ -290,6 +326,9 @@ pagemain(struct pagedata *pd) if ((status = htmlwithin(pd, KELEM_H1, "Timekeeper")) != KCGI_OK || (status = printtimes(pd, times)) != KCGI_OK || (status = khtml_attr(&pd->html, KELEM_SCRIPT, + KATTR_SRC, "scripts/counter.js", KATTR__MAX)) != KCGI_OK || + (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK || + (status = khtml_attr(&pd->html, KELEM_SCRIPT, KATTR_SRC, "scripts/localize.js", KATTR__MAX)) != KCGI_OK || (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK) { freetimesheet(times); diff --git a/scripts/counter.js b/scripts/counter.js @@ -2,7 +2,7 @@ function gettime() { return Math.trunc(Date.now() / 1000) } -function getduration(s) { +function parseduration(s) { let hr = s.match(/(\d+)\s*h/); let mn = s.match(/(\d+)\s*m/); let sc = s.match(/(\d+)\s*s/); @@ -13,20 +13,27 @@ function sn(n) { return String(n).padStart(2, "0") } -function updatetime(element, start) { - let elapsed = gettime() - start - let mn = Math.trunc(elapsed / 60) - let sc = elapsed % 60 +function duration(duration) { + let mn = Math.trunc(duration / 60) + let sc = duration % 60 let hr = Math.trunc(mn / 60) mn = mn % 60 - element.textContent = sn(hr) + ':' + sn(mn) + ':' + sn(sc) + + return sn(hr) + ':' + sn(mn) + ':' + sn(sc) +} + +function updatetime(counter, start, total, total_elapsed) { + let elapsed = gettime() - start + counter.textContent = duration(elapsed) + if (total) + total.textContent = duration(total_elapsed + elapsed) /* if (start.checkVisibility()) */ - setTimeout(updatetime, 1000, element, start) + setTimeout(updatetime, 1000, counter, start, total, total_elapsed) } -let start = document.getElementById("counter") -if (!start) { - console.warn("Expected #counter element") -} else { - updatetime(start, gettime() - getduration(start.dateTime)) +let counter = document.getElementById("counter") +if (counter) { + let total = document.querySelector("table>tfoot>tr>td:nth-child(6)>time") + let d = parseduration(counter.dateTime) + updatetime(counter, gettime() - d, total, parseduration(total.dateTime) - d) } diff --git a/times.c b/times.c @@ -95,8 +95,8 @@ timesheet_set(struct timesheet *ts, enum time_field f, time_t v) ts->set |= timeflagmap[f]; } -struct timesheet * -gettimes(struct pagedata *pd, char *hash) +int +gettimes(struct pagedata *pd, char *hash, struct timesheet **rtimes) { size_t stmtid; struct sqlbox_parm p = { @@ -118,7 +118,7 @@ gettimes(struct pagedata *pd, char *hash) time = newtimesheet(); if (!time) { freetimesheet(times); - return NULL; + return 1; } if (!times) times = time; @@ -143,7 +143,8 @@ gettimes(struct pagedata *pd, char *hash) err(1, "finalise"); } - return times; + *rtimes = times; + return 0; } time_t diff --git a/times.h b/times.h @@ -29,5 +29,5 @@ void freetimesheet(struct timesheet *ts); struct timesheet *newtimesheet(void); struct timesheet *inserttimesheet(struct timesheet *ts, struct timesheet *list); void timesheet_set(struct timesheet *ts, enum time_field f, time_t v); -struct timesheet *gettimes(struct pagedata *pd, char *hash); +int gettimes(struct pagedata *pd, char *hash, struct timesheet **rtimes); time_t getduration(struct timesheet *ts);