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:
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, ×))
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, ×))
+ 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);