Browse Source

added more buttons and updated void func to be a more generic update status func

cli-archive
nic 2 years ago
parent
commit
dd1ce16efb
  1. 6
      apps/web/main.go
  2. 4
      internal/api/invoices.go
  3. 39
      internal/handlers/web/invoices.go
  4. 95
      static/css/styles.css
  5. 44
      templates/partials/invoice_search_results.html

6
apps/web/main.go

@ -27,7 +27,11 @@ func main() {
protected.HandleFunc("/", web.DashboardHandler).Methods("GET") protected.HandleFunc("/", web.DashboardHandler).Methods("GET")
protected.HandleFunc("/jobs", web.JobsHandler).Methods("GET") protected.HandleFunc("/jobs", web.JobsHandler).Methods("GET")
protected.HandleFunc("/invoices", web.InvoicesHandler).Methods("GET", "POST") protected.HandleFunc("/invoices", web.InvoicesHandler).Methods("GET", "POST")
protected.HandleFunc("/void-invoice/{id}", web.VoidInvoiceHandler).Methods("PUT") protected.HandleFunc("/ok-invoice/{id}", web.UpdateInvoiceStatusHandler).Methods("PUT")
protected.HandleFunc("/fail-invoice/{id}", web.UpdateInvoiceStatusHandler).Methods("PUT")
protected.HandleFunc("/pending-invoice/{id}", web.UpdateInvoiceStatusHandler).Methods("PUT")
protected.HandleFunc("/processed-invoice/{id}", web.UpdateInvoiceStatusHandler).Methods("PUT")
protected.HandleFunc("/void-invoice/{id}", web.UpdateInvoiceStatusHandler).Methods("PUT")
protected.HandleFunc("/admin", web.AdminHandler).Methods("GET") protected.HandleFunc("/admin", web.AdminHandler).Methods("GET")
protected.HandleFunc("/assets", web.AssetsHandler).Methods("GET") protected.HandleFunc("/assets", web.AssetsHandler).Methods("GET")
protected.HandleFunc("/companies", web.CompaniesHandler).Methods("GET") protected.HandleFunc("/companies", web.CompaniesHandler).Methods("GET")

4
internal/api/invoices.go

@ -22,7 +22,7 @@ func (s *Session) GetInvoice(identifier string) (map[string]interface{}, error)
return nil, fmt.Errorf("error making request: %v", err) return nil, fmt.Errorf("error making request: %v", err)
} }
defer resp.Body.Close() defer resp.Body.Close()
fmt.Println(resp.Status)
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, fmt.Errorf("error reading response body: %v", err) return nil, fmt.Errorf("error reading response body: %v", err)
@ -43,7 +43,7 @@ func (s *Session) GetInvoice(identifier string) (map[string]interface{}, error)
} }
// Log the entire response for debugging // Log the entire response for debugging
fmt.Printf("API Response: %+v\n", result) // fmt.Printf("API Response: %+v\n", result)
if isInvoiceNumber { if isInvoiceNumber {
// Handle invoice number case // Handle invoice number case

39
internal/handlers/web/invoices.go

@ -49,12 +49,12 @@ func handleInvoiceSearch(w http.ResponseWriter, r *http.Request, session *api.Se
invoice, err := session.GetInvoice(searchTerm) invoice, err := session.GetInvoice(searchTerm)
log.Printf("GetInvoice result - invoice: %+v, err: %v", invoice, err) // log.Printf("GetInvoice result - invoice: %+v, err: %v", invoice, err)
if err != nil { if err != nil {
log.Printf("Error fetching invoice: %v", err) log.Printf("Error fetching invoice: %v", err)
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
errorMsg := fmt.Sprintf("Error fetching invoice: %v", err) errorMsg := fmt.Sprintf("No invoice found for: %s", searchTerm)
if strings.Contains(err.Error(), "access forbidden") { if strings.Contains(err.Error(), "access forbidden") {
errorMsg = "You do not have permission to view this invoice." errorMsg = "You do not have permission to view this invoice."
} }
@ -83,7 +83,7 @@ func handleInvoiceSearch(w http.ResponseWriter, r *http.Request, session *api.Se
invoice["id"] = fmt.Sprintf("%.0f", id) invoice["id"] = fmt.Sprintf("%.0f", id)
} }
log.Printf("Invoice found: %+v", invoice) // log.Printf("Invoice found: %+v", invoice)
tmpl := template.Must(template.ParseFiles("templates/partials/invoice_search_results.html")) tmpl := template.Must(template.ParseFiles("templates/partials/invoice_search_results.html"))
err = tmpl.ExecuteTemplate(w, "invoice_search_results", invoice) err = tmpl.ExecuteTemplate(w, "invoice_search_results", invoice)
@ -93,7 +93,7 @@ func handleInvoiceSearch(w http.ResponseWriter, r *http.Request, session *api.Se
} }
} }
func VoidInvoiceHandler(w http.ResponseWriter, r *http.Request) { func UpdateInvoiceStatusHandler(w http.ResponseWriter, r *http.Request) {
session, ok := r.Context().Value("session").(*api.Session) session, ok := r.Context().Value("session").(*api.Session)
if !ok { if !ok {
http.Error(w, "Unauthorized", http.StatusUnauthorized) http.Error(w, "Unauthorized", http.StatusUnauthorized)
@ -105,41 +105,56 @@ func VoidInvoiceHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
// Extract the invoice ID from the URL // Extract the invoice ID and status from the URL
parts := strings.Split(r.URL.Path, "/") parts := strings.Split(r.URL.Path, "/")
if len(parts) < 3 { if len(parts) < 3 {
http.Error(w, "Invalid request", http.StatusBadRequest) http.Error(w, "Invalid request", http.StatusBadRequest)
return return
} }
invoiceID := parts[len(parts)-1] invoiceID := parts[len(parts)-1]
status := parts[len(parts)-2]
if invoiceID == "" { if invoiceID == "" {
http.Error(w, "Invalid invoice ID", http.StatusBadRequest) http.Error(w, "Invalid invoice ID", http.StatusBadRequest)
return return
} }
// Validate the status
validStatuses := map[string]bool{
"fail": true,
"ok": true,
"pending": true,
"processed": true,
"void": true,
}
if !validStatuses[status] {
http.Error(w, "Invalid status", http.StatusBadRequest)
return
}
// Prepare the request body // Prepare the request body
requestBody := map[string]string{"status": "void"} requestBody := map[string]string{"status": status}
jsonBody, err := json.Marshal(requestBody) jsonBody, err := json.Marshal(requestBody)
if err != nil { if err != nil {
http.Error(w, "Error preparing request", http.StatusInternalServerError) http.Error(w, "Error preparing request", http.StatusInternalServerError)
return return
} }
// Send the PUT request to void the invoice // Send the PUT request to update the invoice status
endpoint := fmt.Sprintf("/invoice/%s", invoiceID) endpoint := fmt.Sprintf("/invoice/%s", invoiceID)
resp, err := session.DoRequest("PUT", endpoint, bytes.NewBuffer(jsonBody)) resp, err := session.DoRequest("PUT", endpoint, bytes.NewBuffer(jsonBody))
if err != nil { if err != nil {
log.Printf("Error voiding invoice: %v", err) log.Printf("Error updating invoice status: %v", err)
http.Error(w, fmt.Sprintf("Error voiding invoice: %v", err), http.StatusInternalServerError) http.Error(w, fmt.Sprintf("Error updating invoice status: %v", err), http.StatusInternalServerError)
return return
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body) body, _ := io.ReadAll(resp.Body)
log.Printf("Failed to void invoice: %s", body) log.Printf("Failed to update invoice status: %s", body)
http.Error(w, fmt.Sprintf("Failed to void invoice: %s", body), resp.StatusCode) http.Error(w, fmt.Sprintf("Failed to update invoice status: %s", body), resp.StatusCode)
return return
} }
@ -151,7 +166,7 @@ func VoidInvoiceHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
log.Printf("Updated invoice after voiding: %+v", invoice) log.Printf("Updated invoice after status change to %s: %+v", status, invoice)
// Render the updated invoice details // Render the updated invoice details
tmpl := template.Must(template.ParseFiles("templates/partials/invoice_search_results.html")) tmpl := template.Must(template.ParseFiles("templates/partials/invoice_search_results.html"))

95
static/css/styles.css

@ -1,5 +1,6 @@
/* General reset and body setup */ /* General reset and body setup */
body, html { body,
html {
margin: 0; margin: 0;
height: 100%; height: 100%;
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
@ -272,7 +273,8 @@ body, html {
} }
/* Style for buttons or links within submenu items */ /* Style for buttons or links within submenu items */
.submenu-item a, .submenu-item button { .submenu-item a,
.submenu-item button {
display: inline-block; display: inline-block;
background-color: #4299e1; background-color: #4299e1;
color: white; color: white;
@ -286,17 +288,23 @@ body, html {
cursor: pointer; cursor: pointer;
} }
.submenu-item a:hover, .submenu-item button:hover { .submenu-item a:hover,
.submenu-item button:hover {
background-color: #2b6cb0; background-color: #2b6cb0;
} }
/* Style for error and not-found message display */ /* Style for error and not-found message display */
@keyframes fadeIn { @keyframes fadeIn {
from { opacity: 0; } from {
to { opacity: 1; } opacity: 0;
}
to {
opacity: 1;
}
} }
.error, .not-found { .error,
.not-found {
text-align: center; text-align: center;
padding: 0.75rem; padding: 0.75rem;
border-radius: 0.25rem; border-radius: 0.25rem;
@ -317,22 +325,27 @@ body, html {
color: #92400e; color: #92400e;
} }
.error::before, .not-found::before { .error::before,
.not-found::before {
margin-right: 0.5rem; margin-right: 0.5rem;
} }
.error::before { .error::before {
content: '⚠️'; content: "⚠️";
} }
.not-found::before { .not-found::before {
content: '🔍'; content: "🔍";
} }
/* Loading indicator */ /* Loading indicator */
@keyframes spin { @keyframes spin {
0% { transform: rotate(0deg); } 0% {
100% { transform: rotate(360deg); } transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
} }
.htmx-indicator { .htmx-indicator {
@ -367,16 +380,20 @@ body, html {
/* Highlight effect for changed content */ /* Highlight effect for changed content */
@keyframes highlightBackground { @keyframes highlightBackground {
0% { background-color: #fef3c7; } 0% {
100% { background-color: transparent; } background-color: #fef3c7;
}
100% {
background-color: transparent;
}
} }
.highlight-change { .highlight-change {
animation: highlightBackground 2s ease-in-out; animation: highlightBackground 2s ease-in-out;
} }
/* Void button styles */ /* You better be sure you wanna do this button styles */
.void-button { .warning-button {
background-color: #ef4444; background-color: #ef4444;
color: white; color: white;
border: none; border: none;
@ -388,11 +405,55 @@ body, html {
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.void-button:hover { .warning-button:hover {
background-color: #dc2626; background-color: #dc2626;
} }
.void-button:disabled { .warning-button:disabled {
background-color: #9ca3af;
cursor: not-allowed;
}
/* Pretty sure you wanna do this but lets think about it again button styles */
.caution-button {
background-color: #eab308;
color: #1f2937;
border: none;
padding: 0.5rem 1rem;
border-radius: 0.25rem;
font-size: 0.875rem;
cursor: pointer;
transition: background-color 0.2s;
margin-bottom: 1rem;
}
.caution-button:hover {
background-color: #ca8a04;
}
.caution-button:disabled {
background-color: #9ca3af;
cursor: not-allowed;
}
/* This button is harmless button styles */
.success-button {
background-color: #22c55e;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 0.25rem;
font-size: 0.875rem;
cursor: pointer;
transition: background-color 0.2s;
margin-bottom: 1rem;
}
.success-button:hover {
background-color: #16a34a;
}
.success-button:disabled {
background-color: #9ca3af; background-color: #9ca3af;
cursor: not-allowed; cursor: not-allowed;
} }

44
templates/partials/invoice_search_results.html

@ -1,5 +1,5 @@
{{define "invoice_search_results"}} {{if .Error}} {{define "invoice_search_results"}} {{if .Error}}
<div class="error"> <div class="not-found">
<p>{{.ErrorMsg}}</p> <p>{{.ErrorMsg}}</p>
<p>Search term: "{{.SearchTerm}}"</p> <p>Search term: "{{.SearchTerm}}"</p>
</div> </div>
@ -10,12 +10,50 @@
</div> </div>
{{else if .invoiceNumber}} {{else if .invoiceNumber}}
<h3>Invoice Details</h3> <h3>Invoice Details</h3>
{{if ne .status "void"}} {{if and (or (ne .status "void") (ne .status "processed")) (or (eq .status
"draft") (eq .status "ok") (eq .status "pending_accounting") (eq .status
"failed") )}}
<button
hx-put="/draft-invoice/{{.id}}"
hx-confirm="Are you sure you want to void this invoice?"
hx-target="#invoice-search-results"
class="success-button">
Draft Invoice
</button>
<button
hx-put="/ok-invoice/{{.id}}"
hx-confirm="Are you sure you want to void this invoice?"
hx-target="#invoice-search-results"
class="success-button">
Ok Invoice
</button>
<button
hx-put="/fail-invoice/{{.id}}"
hx-confirm="Are you sure you want to void this invoice?"
hx-target="#invoice-search-results"
class="caution-button">
Fail Invoice
</button>
<button
hx-put="/pending-invoice/{{.id}}"
hx-confirm="Are you sure you want to void this invoice?"
hx-target="#invoice-search-results"
class="caution-button">
Pending Invoice
</button>
{{end}} {{if and (ne .status "void") (ne .status "processed")}}
<button
hx-put="/process-invoice/{{.id}}"
hx-confirm="Are you sure you want to void this invoice?"
hx-target="#invoice-search-results"
class="warning-button">
Process Invoice
</button>
<button <button
hx-put="/void-invoice/{{.id}}" hx-put="/void-invoice/{{.id}}"
hx-confirm="Are you sure you want to void this invoice?" hx-confirm="Are you sure you want to void this invoice?"
hx-target="#invoice-search-results" hx-target="#invoice-search-results"
class="void-button"> class="warning-button">
Void Invoice Void Invoice
</button> </button>
{{end}} {{end}}

Loading…
Cancel
Save