Browse Source

updated styles and error handling

cli-archive
nic 1 year ago
parent
commit
c7f49aefec
  1. 49
      internal/api/invoices.go
  2. 34
      internal/handlers/web/invoices.go
  3. 85
      static/css/styles.css
  4. 30
      templates/partials/invoice_search.html
  5. 24
      templates/partials/invoice_search_results.html

49
internal/api/invoices.go

@ -9,7 +9,6 @@ import (
func (s *Session) GetInvoice(identifier string) (map[string]interface{}, error) { func (s *Session) GetInvoice(identifier string) (map[string]interface{}, error) {
var endpoint string var endpoint string
isInvoiceNumber, _ := regexp.MatchString(`^[A-Za-z]`, identifier) isInvoiceNumber, _ := regexp.MatchString(`^[A-Za-z]`, identifier)
if isInvoiceNumber { if isInvoiceNumber {
@ -20,13 +19,22 @@ func (s *Session) GetInvoice(identifier string) (map[string]interface{}, error)
resp, err := s.DoRequest("GET", endpoint, nil) resp, err := s.DoRequest("GET", endpoint, nil)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("error making request: %v", err)
} }
defer resp.Body.Close() defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("error reading response body: %v", err)
}
if resp.StatusCode == 403 {
// Handle 403 Forbidden specifically
return nil, fmt.Errorf("access forbidden: you may not have permission to view this invoice")
}
if resp.StatusCode != 200 { if resp.StatusCode != 200 {
return nil, fmt.Errorf("failed to get invoice info: %s, response: %s", resp.Status, string(body)) return nil, fmt.Errorf("failed to get invoice info: %s", resp.Status)
} }
var result map[string]interface{} var result map[string]interface{}
@ -34,15 +42,30 @@ func (s *Session) GetInvoice(identifier string) (map[string]interface{}, error)
return nil, fmt.Errorf("error unmarshalling response: %v", err) return nil, fmt.Errorf("error unmarshalling response: %v", err)
} }
if data, ok := result["data"].(map[string]interface{}); ok { // Log the entire response for debugging
if invoices, ok := data["invoices"].([]interface{}); ok && len(invoices) > 0 { fmt.Printf("API Response: %+v\n", result)
if invoice, ok := invoices[0].(map[string]interface{}); ok {
return invoice, nil if isInvoiceNumber {
} // Handle invoice number case
} else { data, ok := result["data"].(map[string]interface{})
return data, nil if !ok {
return nil, nil // No invoice found
}
invoices, ok := data["invoices"].([]interface{})
if !ok || len(invoices) == 0 {
return nil, nil // No invoice found
}
invoice, ok := invoices[0].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("unexpected invoice structure")
} }
return invoice, nil
} else {
// Handle invoice ID case
data, ok := result["data"].(map[string]interface{})
if !ok || len(data) == 0 {
return nil, nil // No invoice found
}
return data, nil
} }
return nil, fmt.Errorf("no invoice found in the response")
} }

34
internal/handlers/web/invoices.go

@ -1,6 +1,7 @@
package web package web
import ( import (
"fmt"
"html/template" "html/template"
"log" "log"
"marmic/servicetrade-toolbox/internal/api" "marmic/servicetrade-toolbox/internal/api"
@ -15,7 +16,7 @@ func InvoicesHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
if r.Method == "GET" && r.URL.Query().Get("search") != "" { if r.Method == "GET" {
handleInvoiceSearch(w, r, session) handleInvoiceSearch(w, r, session)
return return
} }
@ -41,15 +42,42 @@ func handleInvoiceSearch(w http.ResponseWriter, r *http.Request, session *api.Se
return return
} }
log.Printf("Searching for invoice with term: %s", searchTerm)
invoice, err := session.GetInvoice(searchTerm) invoice, err := session.GetInvoice(searchTerm)
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.StatusInternalServerError) w.WriteHeader(http.StatusOK)
errorMsg := fmt.Sprintf("Error fetching invoice: %v", err)
if strings.Contains(err.Error(), "access forbidden") {
errorMsg = "You do not have permission to view this invoice."
}
tmpl := template.Must(template.ParseFiles("templates/partials/invoice_search_results.html"))
tmpl.ExecuteTemplate(w, "invoice_search_results", map[string]interface{}{
"Error": true,
"ErrorMsg": errorMsg,
"SearchTerm": searchTerm,
})
return
}
if invoice == nil {
log.Printf("No invoice found for: %s", searchTerm)
w.WriteHeader(http.StatusOK)
tmpl := template.Must(template.ParseFiles("templates/partials/invoice_search_results.html")) tmpl := template.Must(template.ParseFiles("templates/partials/invoice_search_results.html"))
tmpl.ExecuteTemplate(w, "invoice_search_results", map[string]interface{}{"Error": err.Error()}) tmpl.ExecuteTemplate(w, "invoice_search_results", map[string]interface{}{
"NotFound": true,
"ErrorMsg": fmt.Sprintf("No invoice found for: %s", searchTerm),
"SearchTerm": searchTerm,
})
return return
} }
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)
if err != nil { if err != nil {

85
static/css/styles.css

@ -288,4 +288,89 @@ body, html {
.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 */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.error, .not-found {
text-align: center;
padding: 0.75rem;
border-radius: 0.25rem;
font-size: 0.875rem;
margin-top: 0.5rem;
animation: fadeIn 0.3s ease-in-out;
}
.error {
background-color: #fee2e2;
border: 1px solid #ef4444;
color: #b91c1c;
}
.not-found {
background-color: #fef3c7;
border: 1px solid #f59e0b;
color: #92400e;
}
.error::before, .not-found::before {
margin-right: 0.5rem;
}
.error::before {
content: '⚠️';
}
.not-found::before {
content: '🔍';
}
/* Loading indicator */
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.htmx-indicator {
display: none;
}
.htmx-request .htmx-indicator {
display: flex;
align-items: center;
}
.htmx-request.htmx-indicator {
display: flex;
align-items: center;
}
.loading-indicator {
width: 1.5rem;
height: 1.5rem;
border: 0.25rem solid #e2e8f0;
border-top: 0.25rem solid #4299e1;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-right: 0.5rem;
}
/* Fade effect for existing content */
.htmx-request .fade-me-out {
opacity: 0.5;
transition: opacity 0.3s ease-in-out;
}
/* Highlight effect for changed content */
@keyframes highlightBackground {
0% { background-color: #fef3c7; }
100% { background-color: transparent; }
}
.highlight-change {
animation: highlightBackground 2s ease-in-out;
} }

30
templates/partials/invoice_search.html

@ -1,13 +1,21 @@
{{define "invoice_search"}} {{define "invoice_search"}}
<div id="invoice-search"></div> <div id="search-container">
<input <input
type="text" type="text"
name="search" name="search"
id="invoice-search-input" placeholder="Enter invoice number or id"
hx-get="/invoices" hx-get="/invoices"
hx-trigger="keyup changed delay:300ms" hx-trigger="keyup changed delay:500ms"
hx-target="#invoice-search-results" hx-target="#invoice-search-results"
hx-indicator="#invoice-search-spinner" /> hx-indicator="#loading-indicator" />
<div id="invoice-search-spinner" class="htmx-indicator">Searching...</div>
<div id="invoice-search-results"></div> <div id="loading-indicator" class="htmx-indicator">
<span>Searching...</span>
<div class="loading-indicator"></div>
</div>
</div>
<br />
<div id="invoice-search-results" class="fade-me-out">
<!-- This is where the invoice details will be loaded -->
</div>
{{end}} {{end}}

24
templates/partials/invoice_search_results.html

@ -1,16 +1,24 @@
{{define "invoice_search_results"}} {{if .Error}} {{define "invoice_search_results"}} {{if .Error}}
<p class="error">Error: {{.Error}}</p> <div class="error">
<p>{{.ErrorMsg}}</p>
<p>Invoice ID: "{{.SearchTerm}}"</p>
</div>
{{else if .NotFound}}
<div class="not-found">
<p>{{.ErrorMsg}}</p>
<p>Invoice Number: "{{.SearchTerm}}"</p>
</div>
{{else if .invoiceNumber}} {{else if .invoiceNumber}}
<h3>Invoice Details</h3> <h3>Invoice Details</h3>
<p>Invoice Number: {{.invoiceNumber}}</p> <p><strong>Invoice Number:</strong> {{.invoiceNumber}}</p>
<p>Total Price: ${{.totalPrice}}</p> <p><strong>Total Price:</strong> ${{.totalPrice}}</p>
<p>Status: {{.status}}</p> <p><strong>Status:</strong> {{.status}}</p>
{{with .customer}} {{with .customer}}
<p>Customer: {{.name}}</p> <p><strong>Customer:</strong> {{.name}}</p>
{{end}} {{with .job}} {{end}} {{with .job}}
<p>Job: {{.name}}</p> <p><strong>Job:</strong> {{.name}}</p>
{{end}} {{with .location}} {{end}} {{with .location}}
<p>Location: {{.name}}</p> <p><strong>Location:</strong> {{.name}}</p>
{{end}} {{if .items}} {{end}} {{if .items}}
<h4>Items:</h4> <h4>Items:</h4>
<ul> <ul>
@ -19,5 +27,5 @@
{{end}} {{end}}
</ul> </ul>
{{end}} {{else}} {{end}} {{else}}
<p>No invoice found with the given identifier.</p> <p>Unexpected response. Please try again.</p>
{{end}} {{end}} {{end}} {{end}}

Loading…
Cancel
Save