Add the “menu” to change invoice statuses
This commit is contained in:
parent
d8997de654
commit
039bf3abbd
|
@ -29,12 +29,16 @@ type InvoiceEntry struct {
|
|||
}
|
||||
|
||||
type InvoicesIndexPage struct {
|
||||
Invoices []*InvoiceEntry
|
||||
Invoices []*InvoiceEntry
|
||||
InvoiceStatuses map[string]string
|
||||
}
|
||||
|
||||
func IndexInvoices(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
conn := getConn(r)
|
||||
locale := getLocale(r)
|
||||
page := &InvoicesIndexPage{
|
||||
Invoices: mustCollectInvoiceEntries(r.Context(), getConn(r), mustGetCompany(r), getLocale(r)),
|
||||
Invoices: mustCollectInvoiceEntries(r.Context(), conn, mustGetCompany(r), locale),
|
||||
InvoiceStatuses: mustCollectInvoiceStatuses(r.Context(), conn, locale),
|
||||
}
|
||||
mustRenderAppTemplate(w, r, "invoices/index.gohtml", page)
|
||||
}
|
||||
|
@ -58,6 +62,25 @@ func mustCollectInvoiceEntries(ctx context.Context, conn *Conn, company *Company
|
|||
return entries
|
||||
}
|
||||
|
||||
func mustCollectInvoiceStatuses(ctx context.Context, conn *Conn, locale *Locale) map[string]string {
|
||||
rows := conn.MustQuery(ctx, "select invoice_status.invoice_status, isi18n.name from invoice_status join invoice_status_i18n isi18n using(invoice_status) where isi18n.lang_tag = $1 order by invoice_status", locale.Language.String())
|
||||
defer rows.Close()
|
||||
|
||||
statuses := map[string]string{}
|
||||
for rows.Next() {
|
||||
var key, name string
|
||||
if err := rows.Scan(&key, &name); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
statuses[key] = name
|
||||
}
|
||||
if rows.Err() != nil {
|
||||
panic(rows.Err())
|
||||
}
|
||||
|
||||
return statuses
|
||||
}
|
||||
|
||||
func ServeInvoice(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
locale := getLocale(r)
|
||||
conn := getConn(r)
|
||||
|
@ -555,3 +578,21 @@ func (form *invoiceProductForm) Validate() bool {
|
|||
validator.CheckAtMostOneOfEachGroup(form.Tax, gettext("You can only select a tax of each class.", form.locale))
|
||||
return validator.AllOK()
|
||||
}
|
||||
|
||||
func HandleUpdateInvoice(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
conn := getConn(r)
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if err := verifyCsrfTokenValid(r); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
invoiceStatus := r.FormValue("status")
|
||||
slug := conn.MustGetText(r.Context(), "", "update invoice set invoice_status = $1 where slug = $2 returning slug", invoiceStatus, params[0].Value)
|
||||
if slug == "" {
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
http.Redirect(w, r, companyURI(mustGetCompany(r), "/invoices"), http.StatusSeeOther)
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ func NewRouter(db *Db) http.Handler {
|
|||
companyRouter.GET("/invoices", IndexInvoices)
|
||||
companyRouter.POST("/invoices", HandleAddInvoice)
|
||||
companyRouter.GET("/invoices/:slug", ServeInvoice)
|
||||
companyRouter.PUT("/invoices/:slug", HandleUpdateInvoice)
|
||||
companyRouter.POST("/invoices/new", HandleNewInvoiceAction)
|
||||
companyRouter.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
mustRenderAppTemplate(w, r, "dashboard.gohtml", nil)
|
||||
|
|
|
@ -146,8 +146,11 @@
|
|||
--numerus--color--white: #ffffff;
|
||||
--numerus--color--yellow: #ffd200;
|
||||
--numerus--color--red: #ff7a53;
|
||||
--numerus--color--rosy: #ffbaa6;
|
||||
--numerus--color--green: #5ae487;
|
||||
--numerus--color--light-green: #9fefb9;
|
||||
--numerus--color--blue: #55bfff;
|
||||
--numerus--color--light-blue: #cbebff;
|
||||
--numerus--color--hay: #ffe673;
|
||||
|
||||
--numerus--text-color: var(--numerus--color--black);
|
||||
|
@ -232,6 +235,10 @@ table {
|
|||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.no-padding td {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
tbody tr:nth-child(even) {
|
||||
background-color: var(--numerus--header--background-color);
|
||||
}
|
||||
|
@ -439,7 +446,7 @@ fieldset {
|
|||
background-color: var(--numerus--background-color);
|
||||
}
|
||||
|
||||
#profile-menu[open] summary::before {
|
||||
details.menu[open] summary::before {
|
||||
background-color: var(--numerus--header--background-color);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
|
@ -452,8 +459,12 @@ fieldset {
|
|||
mix-blend-mode: multiply;
|
||||
}
|
||||
|
||||
#profile-menu ul {
|
||||
ul[role="menu"] {
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
#profile-menu ul {
|
||||
position: absolute;
|
||||
right: -1.875em;
|
||||
top: 100%;
|
||||
|
@ -517,6 +528,53 @@ main > nav {
|
|||
text-decoration: none;
|
||||
}
|
||||
|
||||
.invoice-status {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.invoice-status summary {
|
||||
height: 3rem;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.invoice-status summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.invoice-status ul {
|
||||
position: absolute;
|
||||
top: 1.5em;
|
||||
left: 0;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.invoice-status button {
|
||||
border: 0;
|
||||
min-width: 15rem;
|
||||
}
|
||||
|
||||
[class^='invoice-status-'] {
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.invoice-status-created {
|
||||
background-color: var(--numerus--color--light-blue);
|
||||
}
|
||||
|
||||
.invoice-status-sent {
|
||||
background-color: var(--numerus--color--hay);
|
||||
}
|
||||
|
||||
.invoice-status-paid {
|
||||
background-color: var(--numerus--color--light-green);
|
||||
}
|
||||
|
||||
.invoice-status-unpaid {
|
||||
background-color: var(--numerus--color--rosy);
|
||||
}
|
||||
|
||||
/* Remix Icon */
|
||||
|
||||
@font-face {
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<body>
|
||||
<header>
|
||||
<h1><img src="/static/numerus.svg" alt="Numerus" width="261" height="33"></h1>
|
||||
<details id="profile-menu">
|
||||
<details id="profile-menu" class="menu">
|
||||
<summary>
|
||||
<i class="ri-eye-close-line ri-3x"></i>
|
||||
</summary>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
</nav>
|
||||
|
||||
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.InvoicesIndexPage*/ -}}
|
||||
<table>
|
||||
<table class="no-padding">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{( pgettext "All" "invoice" )}}</th>
|
||||
|
@ -36,12 +36,31 @@
|
|||
<td>{{ .Date|formatDate }}</td>
|
||||
<td><a href="{{ companyURI "/invoices/"}}{{ .Slug }}">{{ .Number }}</a></td>
|
||||
<td><a href="{{ companyURI "/contacts/"}}{{ .CustomerSlug }}">{{ .CustomerName }}</a></td>
|
||||
<td class="invoice-status-{{ .Status }}">{{ .StatusLabel }}</td>
|
||||
<td>
|
||||
<details class="invoice-status menu">
|
||||
<summary class="invoice-status-{{ .Status }}">{{ .StatusLabel }}</summary>
|
||||
<form action="{{companyURI "/invoices/"}}{{ .Slug }}" method="POST">
|
||||
{{ csrfToken }}
|
||||
{{ putMethod }}
|
||||
<ul role="menu">
|
||||
{{- range $status, $name := $.InvoiceStatuses }}
|
||||
{{- if ne $status $invoice.Status }}
|
||||
<li>
|
||||
<button type="submit" name="status" class="invoice-status-{{ $status }}"
|
||||
value="{{ $status }}">{{ $name }}</button>
|
||||
</li>
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
</ul>
|
||||
</form>
|
||||
</details>
|
||||
</td>
|
||||
<td></td>
|
||||
<td class="numeric">{{ .Total|formatPrice }}</td>
|
||||
<td class="invoice-download"><a href="{{ companyURI "/invoices/"}}{{ .Slug }}.pdf" download="{{ .Number}}.pdf"
|
||||
title="{{( pgettext "Download invoice" "action" )}}"
|
||||
aria-label="{{( pgettext "Download invoice %s" "action" )}}"><i
|
||||
<td class="invoice-download"><a href="{{ companyURI "/invoices/"}}{{ .Slug }}.pdf"
|
||||
download="{{ .Number}}.pdf"
|
||||
title="{{( pgettext "Download invoice" "action" )}}"
|
||||
aria-label="{{( pgettext "Download invoice %s" "action" )}}"><i
|
||||
class="ri-download-line"></i></a></td>
|
||||
</tr>
|
||||
{{- end }}
|
||||
|
|
Loading…
Reference in New Issue