--- /dev/null
+#!/bin/sh
+
+_EXEC="$(realpath "${0%/*}")"
+. "$_EXEC/cgilite/cgilite.sh"
+. "$_EXEC/cgilite/storage.sh"
+
+debug(){
+ if [ $# = 0 ]; then
+ tee /dev/stderr
+ else
+ printf %s\\n "$*" >/dev/stderr
+ fi
+}
+
+timeid(){
+ # generate time based ID
+ # Fixme: Unix time stamps assumed to be 32bit always
+ d=$(date +%s)
+ { printf $(
+ while [ "$d" -gt 0 ]; do
+ printf \\%o\\n $((d % 256))
+ d=$((d / 256))
+ done |tac |tr -d \\n
+ )
+ head -c5 /dev/urandom
+ } \
+ | uuencode -m - \
+ | sed -n '2{y;+/;:_;;p}'
+}
+
+checkid(){
+ grep -m 1 -xE '[0-9a-zA-Z:_]{12}';
+}
+
+yield_page(){
+printf 'Content-Type: text/html; charset=utf-8\r\n\r\n'
+./cgilite/html-sh.sed <<EOF
+[html [head
+ [meta name="viewport" content="width=device-width"]
+ [link rel="stylesheet" type="text/css" href="invoices.css"]
+ [title Invoices]
+] [body class="$1"
+ [div #menu
+ [a "/invoices/" Invoices]
+ [a "/clients/" Clients]
+ [a "/senders/" Senders]
+ ]
+ $(cat)
+] ]
+EOF
+}
+
+edit_client(){
+ id="$1"
+ if [ -f "clients/$id" ]; then
+ read -r address hourly <"clients/$id"
+ fi
+ [ "$address" ] || address="address="
+ [ "$hourly" ] || hourly="hourly="
+ printf '
+ [form method="POST" action="/update_client"
+ [hidden "id" "%s"]
+ <textarea name="address" placeholder="address">
+%s</textarea>
+ [label for=hourly Hourly Rate:]
+ [input #hourly type=number name=hourly value="%s"]
+ [submit "update" "update" Update]
+ ]' \
+ "$(HTML $id)" \
+ "$(UNSTRING "${address#address=}" |HTML)" \
+ "$(UNSTRING "${hourly#hourly=}" |grep -xE '[0-9]+')"
+}
+
+edit_sender(){
+ id="$1"
+ if [ -f "senders/$id" ]; then
+ address="$(cat "senders/$id")"
+ fi
+ [ "$address" ] || address="Name
+Street
+City
+
+Phone:
+000 000000
+
+Tax no.
+xxx / 000 / ###
+"
+ printf '
+ [form method="POST" action="/update_sender"
+ [hidden "id" "%s"]
+ <textarea name="address" placeholder="address">%s</textarea>
+ [submit "update" "update" Update]
+ ]' \
+ "$(HTML $id)" \
+ "$(HTML "${address}")"
+}
+
+list_clients(){
+ [ -d clients/ ] && for c in clients/*; do
+ read -r address hourly <"$c"
+ address="$(UNSTRING "${address#address=}")"
+ [ "$address" ] || address="(no address)"
+ printf '[div .client .address <!--
+ -->%s[a href="/clients/%s" Edit]]
+ ' "$(HTML "$address")" "$(HTML "${c#clients/}")"
+ done
+}
+
+list_senders(){
+ [ -d senders/ ] && for s in senders/*; do
+ address=$(cat "$s")
+ [ "$address" ] || address="(no address)"
+ printf '[div .sender .address <!--
+ -->%s[a href="/senders/%s" Edit]]
+ ' "$(HTML "$address")" "$(HTML "${s#senders/}")"
+ done
+}
+
+list_invoices(){
+ [ -d invoices/ ] && for i in invoices/*; do
+ read -r sender client date number vat<<-EOF
+ sed q "$i"
+ EOF
+ [ -f "senders/${sender#sender=}" ] \
+ && sender="$(sed q "senders/${sender#sender=}")" \
+ || sender="(unset)"
+ [ -f "clients/${client#client=}" ] \
+ && client="$(sed q "client/${client#client=}")" \
+ || client="(unset)"
+ [ "$date" -gt 0 ] \
+ && date="$(date -d @$date +%x)" \
+ || date="(unset)"
+
+ printf '[div .invoice
+ [h2
+ %s]
+ [label From:] %s [label To:] %s [label on] %s
+ [a href="/invoices/%s" Edit]
+ ]' "$(HTML "$number")" "$(HTML "$sender")" \
+ "$(HTML "$client")" "$(HTML "$date")"
+ done
+}
+
+info="$(PATH "${PATH_INFO}")"
+
+case $info in
+ /invoices.css)
+ . "$_EXEC/cgilite/file.sh"
+ FILE "$_EXEC/invoices.css"
+ ;;
+ /clients)
+ { list_clients
+ printf '[a .new href="/clients/%s" New]' "$(timeid)"
+ } | yield_page clients
+ ;;
+ /clients/*)
+ edit_client "${info#/clients/}" |yield_page client
+ ;;
+ /update_client)
+ id="$(POST id |checkid)"
+ if [ "$(POST update)" = update -a "$id" ]; then
+ mkdir -p clients
+ printf 'address=%s hourly=%s' \
+ "$(POST address |STRING)" "$(POST hourly |STRING)" \
+ >"clients/$id"
+ else
+ echo Invalid Data "$(POST id)" "$(POST update)" >&2
+ fi
+ REDIRECT /clients/
+ ;;
+ /senders)
+ { list_senders
+ printf '[a .new href="/senders/%s" New]' "$(timeid)"
+ } | yield_page senders
+ ;;
+ /senders/*)
+ edit_sender "${info#/senders/}" |yield_page sender
+ ;;
+ /update_sender)
+ id="$(POST id |checkid)"
+ if [ "$(POST update)" = update -a "$id" ]; then
+ mkdir -p senders
+ POST address >"senders/$id"
+ fi
+ REDIRECT /senders/
+ ;;
+ /invoices)
+ { list_invoices
+ printf '[a href="/invoices/%s" New]' "$(timeid)"
+ } | yield_page invoices
+ ;;
+ /invoice/*)
+ edit_invoice "${info#/invoices/}" |yield_page invoice
+ ;;
+ *) REDIRECT /invoices
+ ;;
+esac
--- /dev/null
+* {
+ margin: 0; padding: 0;
+ color: inherit; background-color: inherit;
+ font-size: medium; font-weight: normal;
+ text-decoration: none;
+ box-sizing: border-box;
+}
+body {
+ color: black; background-color: white;
+}
+#menu {
+ margin: 0; padding: .5em 1em;
+ border-bottom: 1px solid black;
+}
+#menu a {
+ background-color: #EEF;
+ border: 1px solid black;
+ margin: .5em 1em .5em 0;
+ padding: .125em .5em;
+}
+#menu a:hover {
+ background-color: #FFF;
+ border-color: #888;
+}
+
+.clients .address,
+.senders .address {
+ display: inline-block;
+ background-color: #EEE;
+ white-space: pre;
+ padding: 1em 1em .5em 1em;
+ margin: 1em 0 0 1em;
+ width: 20em;
+}
+.clients .address a,
+.senders .address a,
+a.new {
+ display: block;
+ background-color: #DEF;
+ text-align: center;
+ margin: .5em 3em 0 3em;
+ padding: .125em 1em;
+ border: 1px solid #08F;
+}
+.clients .address a:hover,
+.senders .address a:hover,
+a.new:hover {
+ border-color: #8EF;
+}
+a.new {
+ display: inline-block;
+ margin: 1em;
+}
+
+form textarea,
+form input {
+ padding: .25em .5em;
+ background-color: #FFF;
+ text-align: left;
+}
+.client form,
+.sender form {
+ position: relative;
+ display: inline-block;
+ margin: 1em; padding: 1em;
+ background-color: #EEE;
+ text-align: right;
+}
+.client form textarea,
+.sender form textarea {
+ display: block;
+ min-width: 30em;
+ min-height: 7em;
+ margin-bottom: .5em;
+}
+.sender form textarea {
+ min-height: 13em;
+}
+.client form label[for=hourly] {
+ margin-right: .5em;
+ line-height: 2em;
+ text-align: right;
+ font-weight: bold;
+}
+.client form input#hourly {
+ min-width: 4em; width: 5em;
+ text-align: right;
+}
+.client form button[type=submit],
+.sender form button[type=submit] {
+ display: block;
+ margin: .5em 0 0 auto;
+ padding: .25em .5em;
+ background-color: #DEF;
+ border-radius: .25em;
+ border: 1px solid #08F;
+}