timekeeper

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

commit ab38cbb654cf4838a163e159f33e67176e99bfe3
parent d161a49c751b7ba37c7d552d111b7b19998054cf
Author: Jacob R. Edwards <jacob@jacobedwards.org>
Date:   Sat, 30 Mar 2024 09:15:33 -0700

Add delete time function

To do this, I added the entry member to the timesheet struct which
is set to the entry column in the times table. Using this and a new
statement, the interface can provide a button next to displayed
entries to delete them.

Diffstat:
Mcss/timesheet.css | 13+++++++++----
Mpages/main.c | 74+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Mstmt.c | 7+++++--
Mstmt.h | 1+
Mtimes.c | 22+++++++++++++++++++---
Mtimes.h | 2++
6 files changed, 97 insertions(+), 22 deletions(-)

diff --git a/css/timesheet.css b/css/timesheet.css @@ -1,10 +1,15 @@ -table, tr, th, td { +table { + border-collapse: collapse; + border-spacing: 0; +} + +tr > :nth-child(-n+6) { border-collapse: collapse; border-spacing: 0; border: 1px solid black; } -tr:nth-child(even) { +tr:nth-child(even) > :nth-child(-n+6) { background-color: #F3F3F3; } @@ -13,7 +18,7 @@ th, td { padding: 0.2rem; } -th[scope=col] { +th[scope=col]:nth-child(-n+6) { background-color: lightblue; } @@ -21,7 +26,7 @@ td > form { display: inline; } -tr:nth-child(even) > td > form > input[type="submit"] { +tr:nth-child(even) > td:nth-child(-n+6) > form > input[type="submit"] { background-color: white; } diff --git a/pages/main.c b/pages/main.c @@ -193,12 +193,52 @@ printtime(struct pagedata *pd, struct timesheet *ts) } enum kcgi_err +printtimefunc(struct pagedata *pd, struct timesheet *ts, + char *key, char *name) +{ + enum kcgi_err status; + + if ((status = khtml_elem(&pd->html, KELEM_TD)) != KCGI_OK || + (status = khtml_elem(&pd->html, KELEM_FORM)) != KCGI_OK || + (status = khtml_attrx(&pd->html, KELEM_INPUT, + KATTR_TYPE, KATTRX_STRING, "hidden", + KATTR_NAME, KATTRX_STRING, key, + KATTR_VALUE, KATTRX_INT, (uint64_t)ts->entry, + KATTR__MAX)) != KCGI_OK || + (status = khtml_attrx(&pd->html, KELEM_INPUT, + KATTR_TYPE, KATTRX_STRING, "submit", + KATTR_VALUE, KATTRX_STRING, name, + KATTR__MAX)) != KCGI_OK || + (status = khtml_closeelem(&pd->html, 2)) != KCGI_OK) + return status; + return KCGI_OK; +} + +enum kcgi_err +printtimefuncs(struct pagedata *pd, struct timesheet *ts) +{ + enum kcgi_err status; + + if (ts->set & StartTimeFlag && + (status = printtimefunc(pd, ts, "delete", "Delete")) != KCGI_OK) + return status; + return KCGI_OK; +} + +int +iscurrent(struct pagedata *pd) +{ + return (pd->req.fieldmap[KeyPeriod] ? + pd->req.fieldmap[KeyPeriod]->parsed.i : 0) == 0; +} + +enum kcgi_err printtimes(struct pagedata *pd, struct timesheet *times) { unsigned int i; enum kcgi_err status; char *headers[] = { - "Date", "Start time", "Break start", "Break end", "End time", "Hours" + "Date", "Start time", "Break start", "Break end", "End time", "Hours", "" }; struct timesheet *p; @@ -215,7 +255,7 @@ printtimes(struct pagedata *pd, struct timesheet *times) if ((status = khtml_attr(&pd->html, KELEM_TH, KATTR_SCOPE, "col", KATTR__MAX)) != KCGI_OK || (status = khtml_puts(&pd->html, headers[i])) != KCGI_OK || - (i == Len(headers) - 1 && ( + (i == 5 && ( (status = khtml_attr(&pd->html, KELEM_A, KATTR_HREF, "#total", KATTR__MAX)) != KCGI_OK || (status = khtml_ncr(&pd->html, 0x2193)) != KCGI_OK || @@ -233,6 +273,7 @@ printtimes(struct pagedata *pd, struct timesheet *times) for (; p; p = p->prev) { if ((status = khtml_elem(&pd->html, KELEM_TR)) != KCGI_OK || (status = printtime(pd, p)) != KCGI_OK || + (iscurrent(pd) && (status = printtimefuncs(pd, p)) != KCGI_OK) || (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK) return status; } @@ -353,17 +394,24 @@ pagemain(struct pagedata *pd) pd->req.fieldmap[KeyPeriod]->parsed.i : 0; current = period == 0; - if (current && pd->req.fieldmap[KeyBreak]) { - if (breaktime(pd, pd->user->hash)) - err(1, "Unable to break time"); - } - - if (current && pd->req.fieldmap[KeyTime]) { - tf = gettf(pd->req.fieldmap[KeyTime]->parsed.s); - if (tf < 0) - err(1, "Invalid time field"); - if (settime(pd, pd->user->hash, tf, time(NULL))) - return KCGI_SYSTEM; + if (current) { + if (pd->req.fieldmap[KeyBreak]) { + if (breaktime(pd, pd->user->hash)) + err(1, "Unable to break time"); + } + if (pd->req.fieldmap[KeyTime]) { + tf = gettf(pd->req.fieldmap[KeyTime]->parsed.s); + if (tf < 0) + err(1, "Invalid time field"); + if (settime(pd, pd->user->hash, tf, time(NULL))) + return KCGI_SYSTEM; + } + if (pd->req.fieldmap[KeyDelete]) { + if (!kvalid_uint(pd->req.fieldmap[KeyDelete])) + return KCGI_SYSTEM; + if (deletetime(pd, pd->user->hash, pd->req.fieldmap[KeyDelete]->parsed.i)) + return KCGI_SYSTEM; + } } status = tk_startpage(pd, &template, KHTTP_200); diff --git a/stmt.c b/stmt.c @@ -103,11 +103,11 @@ struct sqlbox_pstmt pstmts[] = { " (SELECT (entry) FROM current_entries WHERE userid IS ?)" }, [StmtGetTimes] = { .stmt = - "SELECT period, start, startbreak, endbreak, end FROM times\n" + "SELECT period, entry, start, startbreak, endbreak, end FROM times\n" " WHERE userid IS ? ORDER BY period DESC, entry" }, [StmtGetTimePeriod] = { .stmt = - "SELECT period, start, startbreak, endbreak, end FROM times\n" + "SELECT period, entry, start, startbreak, endbreak, end FROM times\n" " WHERE userid IS ? AND period IS ? ORDER BY entry" }, [StmtBreakTime] = { .stmt = @@ -115,5 +115,8 @@ struct sqlbox_pstmt pstmts[] = { " (SELECT IFNULL(max(period) + 1, 1)\n" " FROM times WHERE userid IS ?)\n" " WHERE userid IS ? AND period ISNULL" + }, + [StmtDeleteTime] = { .stmt = + "DELETE FROM times WHERE userid IS ? AND entry IS ?" } }; diff --git a/stmt.h b/stmt.h @@ -20,6 +20,7 @@ enum StmtID { StmtGetTimes, StmtGetTimePeriod, StmtBreakTime, + StmtDeleteTime, StmtMax }; diff --git a/times.c b/times.c @@ -63,6 +63,18 @@ settime(struct pagedata *pd, char *hash, enum time_field f, time_t time) } int +deletetime(struct pagedata *pd, char *hash, size_t entry) +{ + struct sqlbox_parm params[] = { + { .sparm = hash, .type = SQLBOX_PARM_STRING }, + { .iparm = entry, .type = SQLBOX_PARM_INT } + }; + + return sqlbox_exec(pd->db, pd->dbid, StmtDeleteTime, + Len(params), params, 0) != SQLBOX_CODE_OK; +} + +int breaktime(struct pagedata *pd, char *hash) { struct sqlbox_parm ps[] = { @@ -112,6 +124,7 @@ timesheet_set(struct timesheet *ts, enum time_field f, time_t v) int gettimes(struct pagedata *pd, char *hash, int period, struct timesheet **rtimes) { + static unsigned int nreturn = 6; size_t stmtid; struct sqlbox_parm p[] = { { .sparm = hash, .type = SQLBOX_PARM_STRING }, @@ -130,7 +143,7 @@ gettimes(struct pagedata *pd, char *hash, int period, struct timesheet **rtimes) times = oldtime = NULL; while ((r = sqlbox_step(pd->db, stmtid)) && !(!r->psz && r->code == SQLBOX_CODE_OK)) { - assert(r->psz == 5); + assert(r->psz == nreturn); oldtime = time; time = newtimesheet(); if (!time) { @@ -142,20 +155,23 @@ gettimes(struct pagedata *pd, char *hash, int period, struct timesheet **rtimes) else if (oldtime) inserttimesheet(time, oldtime); - for (i = 0; i < 5; ++i) { + for (i = 0; i < nreturn; ++i) { switch (r->ps[i].type) { case SQLBOX_PARM_NULL: break; case SQLBOX_PARM_INT: if (i == 0) time->period = r->ps[i].iparm; + else if (i == 1) + time->entry = r->ps[i].iparm; else - timesheet_set(time, i - 1, r->ps[i].iparm); + timesheet_set(time, i - (nreturn - 4), r->ps[i].iparm); break; default: err(1, "This is a stmt.c bug"); } } + assert(i >= (nreturn - 4)); } if (!sqlbox_finalise(pd->db, stmtid)) { diff --git a/times.h b/times.h @@ -19,12 +19,14 @@ struct timesheet { enum time_flag set; /* which times are set */ unsigned int period; /* period of the times, 0 if not set */ struct timesheet *prev, *next; /* previous and next entry */ + size_t entry; /* the entry, unique in each user's timesheets */ }; extern char *timefields[]; extern enum time_flag timeflagmap[]; int settime(struct pagedata *pd, char *hash, enum time_field f, time_t time); +int deletetime(struct pagedata *pd, char *hash, size_t entry); int breaktime(struct pagedata *pd, char *hash); void freetimesheet(struct timesheet *ts); struct timesheet *newtimesheet(void);