Browse Source

fix: logic before multi invoice just re-ran invoice query after changing invoice status; with multi invoices this is bad; created individual invoice card to update just single invoices when changing statuses

document-upload-removal-layout-update
nic 9 months ago
parent
commit
6900291685
  1. 81
      internal/handlers/web/invoices.go
  2. 54
      templates/partials/invoice_card.html
  3. 81
      templates/partials/invoice_search_results.html

81
internal/handlers/web/invoices.go

@ -160,6 +160,9 @@ func handleSingleInvoice(w http.ResponseWriter, invoiceID string, session *api.S
// Add the buttons to display // Add the buttons to display
invoice["buttons"] = getInvoiceStatusButtons(invoice["id"].(string), invoice["status"].(string)) invoice["buttons"] = getInvoiceStatusButtons(invoice["id"].(string), invoice["status"].(string))
// Add the search term to the template data
invoice["SearchTerm"] = invoiceID
err = tmpl.ExecuteTemplate(w, "invoice_search_results", invoice) err = tmpl.ExecuteTemplate(w, "invoice_search_results", invoice)
if err != nil { if err != nil {
log.Printf("Error executing template: %v", err) log.Printf("Error executing template: %v", err)
@ -192,6 +195,10 @@ func handleMultipleInvoices(w http.ResponseWriter, invoiceIDs []string, session
// Add status buttons // Add status buttons
invoice["buttons"] = getInvoiceStatusButtons(invoice["id"].(string), invoice["status"].(string)) invoice["buttons"] = getInvoiceStatusButtons(invoice["id"].(string), invoice["status"].(string))
// Mark this as part of a multiple invoice view
invoice["MultipleInvoices"] = true
invoice["SearchTerm"] = originalSearchTerm
invoices = append(invoices, invoice) invoices = append(invoices, invoice)
} }
@ -338,7 +345,79 @@ func UpdateInvoiceStatusHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
mockReq, _ := http.NewRequest("GET", fmt.Sprintf("?search=%s", invoiceID), nil) // Check if it's a request for a single invoice update within a multi-invoice view
isPartialUpdate := r.URL.Query().Get("partial") == "true"
if isPartialUpdate {
renderSingleInvoiceUpdate(w, invoiceID, session, r)
return
}
// Get the original search term from request headers or query parameters
originalSearch := r.Header.Get("X-Original-Search")
if originalSearch == "" {
// If not in the header, check query parameters
originalSearch = r.URL.Query().Get("original_search")
}
// If we have the original search term with multiple IDs, use it to preserve the multi-invoice view
// Otherwise, fall back to just showing the updated invoice
searchQuery := invoiceID
if originalSearch != "" {
invoiceIDs := parseInvoiceIDs(originalSearch)
if len(invoiceIDs) > 1 {
searchQuery = originalSearch
}
}
// Re-run the search with either the original search term or just the updated invoice ID
mockReq, _ := http.NewRequest("GET", fmt.Sprintf("?search=%s", searchQuery), nil)
mockReq = mockReq.WithContext(r.Context()) mockReq = mockReq.WithContext(r.Context())
handleInvoiceSearch(w, mockReq, session) handleInvoiceSearch(w, mockReq, session)
} }
// renderSingleInvoiceUpdate fetches a single invoice and renders just its card with hx-swap-oob
// for efficient in-place updates within a multi-invoice view
func renderSingleInvoiceUpdate(w http.ResponseWriter, invoiceID string, session *api.Session, r *http.Request) {
log.Printf("Rendering single invoice update for: %s", invoiceID)
// Get the original search term for maintaining context in buttons
originalSearch := r.Header.Get("X-Original-Search")
if originalSearch == "" {
originalSearch = r.URL.Query().Get("original_search")
}
// Fetch just the updated invoice
invoice, err := session.GetInvoice(invoiceID)
if err != nil {
log.Printf("Error fetching updated invoice: %v", err)
http.Error(w, fmt.Sprintf("Error fetching invoice: %v", err), http.StatusInternalServerError)
return
}
if invoice == nil {
log.Printf("No invoice found for update: %s", invoiceID)
http.Error(w, "Invoice not found", http.StatusNotFound)
return
}
// Format the invoice ID if needed
if id, ok := invoice["id"].(float64); ok {
invoice["id"] = fmt.Sprintf("%.0f", id)
}
// Add status buttons
invoice["buttons"] = getInvoiceStatusButtons(invoice["id"].(string), invoice["status"].(string))
// Add search term and set flag for partial update
invoice["SearchTerm"] = originalSearch
invoice["PartialUpdate"] = true
invoice["InvoiceID"] = invoiceID
// Render only the specific invoice card with hx-swap-oob
tmpl := root.WebTemplates
err = tmpl.ExecuteTemplate(w, "invoice_card", invoice)
if err != nil {
log.Printf("Error executing template for partial update: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}

54
templates/partials/invoice_card.html

@ -0,0 +1,54 @@
{{define "invoice_card"}}
<div id="invoice-card-{{.invoiceNumber}}" class="invoice-card" {{if .PartialUpdate}}hx-swap-oob="true" {{end}}>
<div class="invoice-header">
<h4>Invoice #{{.invoiceNumber}}</h4>
<div class="invoice-status {{.status}}">{{.status}}</div>
</div>
<div class="invoice-details">
<div class="invoice-info">
{{with .customer}}<p><strong>Customer:</strong> {{.name}}</p>{{end}}
{{with .job}}<p><strong>Job:</strong> {{.name}}</p>{{end}}
{{/* Show location only in single invoice view */}}
{{if not .MultipleInvoices}}
{{with .location}}<p><strong>Location:</strong> {{.name}}</p>{{end}}
{{end}}
<p><strong>Total:</strong> ${{.totalPrice}}</p>
{{/* Show items only in single invoice view */}}
{{if not .MultipleInvoices}}
{{if .items}}
<div class="invoice-items">
<h5>Items</h5>
<ul>
{{range .items}}
<li>{{.description}} - ${{.totalPrice}}</li>
{{end}}
</ul>
</div>
{{end}}
{{end}}
</div>
<div class="invoice-actions">
<div class="button-group">
{{range .buttons}}
<button hx-put="{{.Action}}?partial=true&original_search={{$.SearchTerm}}" hx-confirm="{{.ConfirmText}}"
hx-target="#invoice-search-results" hx-headers='{"X-Original-Search": "{{$.SearchTerm}}"}'
class="compact-button {{.Class}}" title="{{.Label}}" data-status="{{.Status}}">
<span class="icon">
{{if eq .Status "draft"}}✏️
{{else if eq .Status "ok"}}🆗
{{else if eq .Status "failed"}}❌
{{else if eq .Status "pending_accounting"}}🧾
{{else if eq .Status "processed"}}💰
{{else if eq .Status "void"}} 💣
{{end}}
</span>
</button>
{{end}}
</div>
</div>
</div>
</div>
{{end}}

81
templates/partials/invoice_search_results.html

@ -23,91 +23,14 @@
</div> </div>
{{range .Invoices}} {{range .Invoices}}
<div class="invoice-card"> {{template "invoice_card" .}}
<div class="invoice-header">
<h4>Invoice #{{.invoiceNumber}}</h4>
<div class="invoice-status {{.status}}">{{.status}}</div>
</div>
<div class="invoice-details">
<div class="invoice-info">
{{with .customer}}<p><strong>Customer:</strong> {{.name}}</p>{{end}}
{{with .job}}<p><strong>Job:</strong> {{.name}}</p>{{end}}
<p><strong>Total:</strong> ${{.totalPrice}}</p>
</div>
<div class="invoice-actions">
<div class="button-group">
{{range .buttons}}
<button hx-put="{{.Action}}" hx-confirm="{{.ConfirmText}}" hx-target="#invoice-search-results"
class="compact-button {{.Class}}" title="{{.Label}}" data-status="{{.Status}}">
<span class="icon">
{{if eq .Status "draft"}}✏️
{{else if eq .Status "ok"}}✔️
{{else if eq .Status "failed"}}❌
{{else if eq .Status "pending_accounting"}}🧾
{{else if eq .Status "processed"}}💰
{{else if eq .Status "void"}} 🛑
{{end}}
</span>
</button>
{{end}}
</div>
</div>
</div>
</div>
{{end}} {{end}}
</div> </div>
{{else if .invoiceNumber}} {{else if .invoiceNumber}}
<div class="multiple-invoices"> <div class="multiple-invoices">
<h3>Invoice Details</h3> <h3>Invoice Details</h3>
{{template "invoice_card" .}}
<div class="invoice-card">
<div class="invoice-header">
<h4>Invoice #{{.invoiceNumber}}</h4>
<div class="invoice-status {{.status}}">{{.status}}</div>
</div>
<div class="invoice-details">
<div class="invoice-info">
{{with .customer}}<p><strong>Customer:</strong> {{.name}}</p>{{end}}
{{with .job}}<p><strong>Job:</strong> {{.name}}</p>{{end}}
{{with .location}}<p><strong>Location:</strong> {{.name}}</p>{{end}}
<p><strong>Total Price:</strong> ${{.totalPrice}}</p>
{{if .items}}
<div class="invoice-items">
<h5>Items</h5>
<ul>
{{range .items}}
<li>{{.description}} - ${{.totalPrice}}</li>
{{end}}
</ul>
</div>
{{end}}
</div>
<div class="invoice-actions">
<div class="button-group">
{{range .buttons}}
<button hx-put="{{.Action}}" hx-confirm="{{.ConfirmText}}" hx-target="#invoice-search-results"
class="compact-button {{.Class}}" title="{{.Label}}" data-status="{{.Status}}">
<span class="icon">
{{if eq .Status "draft"}}📝
{{else if eq .Status "ok"}}✅
{{else if eq .Status "failed"}}❌
{{else if eq .Status "pending_accounting"}}⏳
{{else if eq .Status "processed"}}📊
{{else if eq .Status "void"}}🚫
{{end}}
</span>
</button>
{{end}}
</div>
</div>
</div>
</div>
</div> </div>
{{else}} {{else}}

Loading…
Cancel
Save