feat: event editing — change exchange rate and currency
- Added Edit button on open event cards in dashboard
- EditEvent handler returns dashboard.html with pre-filled form
- UpdateEvent handler (PUT /events/{id}) saves new currency + rate
- UpdateEvent in db.go updates base_currency and exchange_rate
- Form auto-switches between create (POST) and edit (PUT) mode
- Reuses the same conversion sample pattern as event creation
This commit is contained in:
parent
4ea3d63b29
commit
5f5f2be75b
4 changed files with 105 additions and 6 deletions
|
|
@ -287,6 +287,16 @@ func UpdateEventStatus(db *sql.DB, id, status string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateEvent updates the base currency and exchange rate of an existing event.
|
||||||
|
func UpdateEvent(db *sql.DB, id, baseCurrency string, exchangeRate float64) error {
|
||||||
|
_, err := db.Exec("UPDATE events SET base_currency = ?, exchange_rate = ? WHERE id = ?", baseCurrency, exchangeRate, id)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("ERROR [%s] database: UpdateEvent(%s): %v",
|
||||||
|
time.Now().Format(time.RFC3339), id, err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Expense queries
|
// Expense queries
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -259,3 +259,71 @@ func (h *EventHandler) ViewEventExpenses(w http.ResponseWriter, r *http.Request)
|
||||||
time.Now().Format(time.RFC3339), err)
|
time.Now().Format(time.RFC3339), err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// GET /events/{id}/edit — EditEvent
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// EditEvent returns the event edit form pre-filled with current event data.
|
||||||
|
func (h *EventHandler) EditEvent(w http.ResponseWriter, r *http.Request) {
|
||||||
|
eventID := chi.URLParam(r, "id")
|
||||||
|
event, err := database.GetEventByID(h.DB, eventID)
|
||||||
|
if err != nil || event == nil || event.UserID != getUserID(r) {
|
||||||
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl := getTemplate("dashboard.html")
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"Event": event,
|
||||||
|
"Editing": true,
|
||||||
|
"Events": []database.Event{},
|
||||||
|
"BaseCurrency": event.BaseCurrency,
|
||||||
|
"ExchangeRate": event.ExchangeRate,
|
||||||
|
"LastSampleRcpt": "",
|
||||||
|
"LastSampleClm": "",
|
||||||
|
}
|
||||||
|
if err := tmpl.Execute(w, data); err != nil {
|
||||||
|
log.Printf("ERROR [%s] handlers: EditEvent: execute template: %v",
|
||||||
|
time.Now().Format(time.RFC3339), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// PUT /events/{id} — UpdateEvent
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// UpdateEvent updates the event's base currency and exchange rate.
|
||||||
|
func (h *EventHandler) UpdateEvent(w http.ResponseWriter, r *http.Request) {
|
||||||
|
eventID := chi.URLParam(r, "id")
|
||||||
|
event, err := database.GetEventByID(h.DB, eventID)
|
||||||
|
if err != nil || event == nil || event.UserID != getUserID(r) {
|
||||||
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
baseCurrency := r.FormValue("base_currency")
|
||||||
|
if baseCurrency == "" {
|
||||||
|
baseCurrency = "USD"
|
||||||
|
}
|
||||||
|
|
||||||
|
exchangeRate := 1.0
|
||||||
|
sampleReceipt := r.FormValue("sample_receipt_amount")
|
||||||
|
sampleClaim := r.FormValue("sample_claim_amount")
|
||||||
|
if sampleReceipt != "" && sampleClaim != "" {
|
||||||
|
sampleReceiptVal, err1 := strconv.ParseFloat(sampleReceipt, 64)
|
||||||
|
sampleClaimVal, err2 := strconv.ParseFloat(sampleClaim, 64)
|
||||||
|
if err1 == nil && err2 == nil && sampleReceiptVal > 0 && sampleClaimVal > 0 {
|
||||||
|
exchangeRate = sampleClaimVal / sampleReceiptVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := database.UpdateEvent(h.DB, eventID, baseCurrency, exchangeRate); err != nil {
|
||||||
|
log.Printf("ERROR [%s] handlers: UpdateEvent: %v", time.Now().Format(time.RFC3339), err)
|
||||||
|
http.Error(w, "Failed to update event", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("HX-Redirect", "/dashboard")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
|
|
||||||
2
main.go
2
main.go
|
|
@ -205,6 +205,8 @@ func main() {
|
||||||
// Events.
|
// Events.
|
||||||
r.Get("/dashboard", eventHandler.Dashboard)
|
r.Get("/dashboard", eventHandler.Dashboard)
|
||||||
r.Post("/events", eventHandler.CreateEvent)
|
r.Post("/events", eventHandler.CreateEvent)
|
||||||
|
r.Put("/events/{id}", eventHandler.UpdateEvent)
|
||||||
|
r.Get("/events/{id}/edit", eventHandler.EditEvent)
|
||||||
r.Put("/events/{id}/reopen", eventHandler.ReopenEvent)
|
r.Put("/events/{id}/reopen", eventHandler.ReopenEvent)
|
||||||
r.Get("/events/{id}/expenses", eventHandler.ViewEventExpenses)
|
r.Get("/events/{id}/expenses", eventHandler.ViewEventExpenses)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,20 +28,26 @@
|
||||||
|
|
||||||
<div id="create-event-form" class="hidden" style="margin-bottom: 1rem;">
|
<div id="create-event-form" class="hidden" style="margin-bottom: 1rem;">
|
||||||
<div class="card" style="padding: 1rem;">
|
<div class="card" style="padding: 1rem;">
|
||||||
|
{{if .Editing}}
|
||||||
|
<h3 style="margin-bottom: 1rem;">Edit Event</h3>
|
||||||
|
<form hx-put="/events/{{.Event.ID}}" hx-target="body" hx-push-url="true">
|
||||||
|
{{else}}
|
||||||
<form hx-post="/events" hx-target="body" hx-push-url="true">
|
<form hx-post="/events" hx-target="body" hx-push-url="true">
|
||||||
|
{{end}}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="text" id="name" name="name" placeholder="Event name, e.g. WebSummit 2026" required>
|
<label class="form-label">{{if .Editing}}Event{{else}}Event Name{{end}}</label>
|
||||||
|
<input type="text" id="name" name="name" placeholder="Event name" value="{{.Event.Name}}" {{if not .Editing}}required{{end}}>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">Claim Currency</label>
|
<label class="form-label">Claim Currency</label>
|
||||||
<input type="text" id="base_currency" name="base_currency" value="USD" placeholder="USD, EUR, KES…" required maxlength="3" style="text-transform: uppercase;">
|
<input type="text" id="base_currency" name="base_currency" value="{{if .Editing}}{{.Event.BaseCurrency}}{{else}}USD{{end}}" placeholder="USD, EUR, KES…" required maxlength="3" style="text-transform: uppercase;">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="background: #064e3b; border: 1px solid #065f46; border-radius: 0.5rem; padding: 0.75rem; margin-bottom: 0.5rem;">
|
<div style="background: #064e3b; border: 1px solid #065f46; border-radius: 0.5rem; padding: 0.75rem; margin-bottom: 0.5rem;">
|
||||||
<div style="font-size: 0.8rem; font-weight: 600; color: #6ee7b7; margin-bottom: 0.5rem;">Conversion Sample</div>
|
<div style="font-size: 0.8rem; font-weight: 600; color: #6ee7b7; margin-bottom: 0.5rem;">Conversion Sample</div>
|
||||||
<p style="font-size: 0.75rem; color: var(--color-text-muted); margin-bottom: 0.5rem;">
|
<p style="font-size: 0.75rem; color: var(--color-text-muted); margin-bottom: 0.5rem;">
|
||||||
From a payment notification: receipt amount and what you were charged.
|
{{if .Editing}}Update the sample to recalculate the rate.{{else}}From a payment notification: receipt amount and what you were charged.{{end}}
|
||||||
</p>
|
</p>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|
@ -50,15 +56,19 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" style="flex: 0 0 110px;">
|
<div class="form-group" style="flex: 0 0 110px;">
|
||||||
<label class="form-label">Currency</label>
|
<label class="form-label">Currency</label>
|
||||||
<input type="text" id="sample_receipt_currency" name="sample_receipt_currency" value="KES" placeholder="KES" required maxlength="3" style="text-transform: uppercase;">
|
<input type="text" id="sample_receipt_currency" name="sample_receipt_currency" value="{{if .Editing}}{{.Event.BaseCurrency}}{{else}}KES{{end}}" placeholder="KES" required maxlength="3" style="text-transform: uppercase;">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">What you were charged (claim currency)</label>
|
<label class="form-label">{{if .Editing}}Converted amount{{else}}What you were charged (claim currency){{end}}</label>
|
||||||
<input type="number" id="sample_claim_amount" name="sample_claim_amount" step="0.01" min="0.01" placeholder="e.g. 7.73" required>
|
<input type="number" id="sample_claim_amount" name="sample_claim_amount" step="0.01" min="0.01" placeholder="e.g. 7.73" required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary btn-block">Create Event</button>
|
<button type="submit" class="btn btn-primary btn-block">{{if .Editing}}Save Changes{{else}}Create Event{{end}}</button>
|
||||||
|
{{if .Editing}}
|
||||||
|
<a href="/dashboard" class="btn btn-secondary btn-block" style="display: block; text-align: center; margin-top: 0.5rem;"
|
||||||
|
hx-get="/dashboard" hx-target="body" hx-push-url="true">Cancel</a>
|
||||||
|
{{end}}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -80,6 +90,15 @@
|
||||||
style="text-decoration: none;">
|
style="text-decoration: none;">
|
||||||
Open
|
Open
|
||||||
</a>
|
</a>
|
||||||
|
{{if eq .Status "open"}}
|
||||||
|
<button class="btn btn-secondary btn-sm"
|
||||||
|
hx-get="/events/{{.ID}}/edit"
|
||||||
|
hx-target="#create-event-form"
|
||||||
|
hx-swap="innerHTML"
|
||||||
|
style="font-size: 0.75rem;">
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
{{end}}
|
||||||
{{if eq .Status "closed"}}
|
{{if eq .Status "closed"}}
|
||||||
<button class="btn btn-secondary btn-sm"
|
<button class="btn btn-secondary btn-sm"
|
||||||
hx-put="/events/{{.ID}}/reopen"
|
hx-put="/events/{{.ID}}/reopen"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue