+
-
-
- | Name | Phone | WhatsApp | Actions |
-
-
-`))
- for _, c := range clients {
- var whatsappCell string
- connected := false
- if c.WhatsAppNumber != "" && a.WAConnector != nil {
- connected, _ = a.WAConnector.IsConnected(r.Context(), c.ClientID)
+`)
+
+ if len(clients) == 0 {
+ fmt.Fprint(buf, templates.EmptyState(``, "No clients found. Add your first client to get started."))
+ } else {
+ fmt.Fprintf(buf, ``)
+ fmt.Fprint(buf, templates.TableStart([]string{"Name", "Phone", "WhatsApp", "Created", "Actions"}))
+ for _, c := range clients {
+ var whatsappCell string
+ if c.WhatsAppNumber == "" {
+ whatsappCell = fmt.Sprintf(`
Connect`, c.ClientID)
+ } else {
+ connected := false
+ if a.WAConnector != nil {
+ connected, _ = a.WAConnector.IsConnected(r.Context(), c.ClientID)
+ }
+ if connected {
+ whatsappCell = fmt.Sprintf(`
%s`, htmlEscape(c.WhatsAppNumber))
+ } else if c.WhatsAppConnected == 1 {
+ whatsappCell = fmt.Sprintf(`
%sreconnect`, htmlEscape(c.WhatsAppNumber), c.ClientID)
+ } else {
+ whatsappCell = fmt.Sprintf(`
Connect`, c.ClientID)
+ }
+ }
+ created := time.Unix(c.CreatedAt, 0).Format("02/01/2006")
+ fmt.Fprintf(buf, `
+ | %s |
+ %s |
+ %s |
+ %s |
+
+
+ View
+
+
+
+ |
+
`, htmlEscape(c.Name), htmlEscape(c.Phone), whatsappCell, created, c.ClientID, c.ClientID, c.ClientID)
}
- if connected {
- whatsappCell = c.WhatsAppNumber + `
●`
- } else if c.WhatsAppConnected == 1 && c.WhatsAppNumber != "" {
- whatsappCell = c.WhatsAppNumber + `
○ Reconnect`
- } else {
- whatsappCell = `
Connect`
+ fmt.Fprint(buf, templates.TableEnd())
+ fmt.Fprintf(buf, `
`)
+
+ // Edit forms (rendered below table, toggled via JS)
+ for _, c := range clients {
+ fmt.Fprintf(buf, `
+`, c.ClientID, htmlEscape(c.Name), c.ClientID, htmlEscape(c.Name), htmlEscape(c.Phone), htmlEscape(c.Email), htmlEscape(c.Address), htmlEscape(c.Notes), c.ClientID)
}
- w.Write([]byte(`
- | ` + c.Name + ` |
- ` + c.Phone + ` |
- ` + whatsappCell + ` |
-
- View
-
-
- |
|
-
- |
-
- `))
}
- w.Write([]byte(`
-
Back to Home
-`))
+
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+ templates.WritePage(w, buf, "Clients", "clients")
}
func (a *App) CreateClient(w http.ResponseWriter, r *http.Request) {
@@ -121,7 +142,6 @@ func (a *App) CreateClient(w http.ResponseWriter, r *http.Request) {
return
}
- w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Header().Set("HX-Refresh", "true")
}
@@ -138,43 +158,76 @@ func (a *App) ViewClient(w http.ResponseWriter, r *http.Request) {
return
}
- w.Header().Set("Content-Type", "text/html; charset=utf-8")
+ buf := templates.BufRender()
+ fmt.Fprint(buf, templates.PageHeader(htmlEscape(client.Name), "Client details and WhatsApp connection"))
- var whatsappSection string
+ waSection := ""
if client.WhatsAppNumber != "" {
connected := false
if a.WAConnector != nil {
connected, _ = a.WAConnector.IsConnected(r.Context(), client.ClientID)
}
- status := "Not connected"
- style := "color:#999"
+ statusPill := `
Disconnected`
if connected {
- status = "Connected"
- style = "color:#28a745"
+ statusPill = `
Connected`
} else if client.WhatsAppConnected == 1 {
- status = "Disconnected (was connected)"
- style = "color:#dc3545"
+ statusPill = `
Stale`
}
- whatsappSection = `
-
WhatsApp: ` + client.WhatsAppNumber + ` (` + status + `)
-
Reconnect WhatsApp
`
+ waSection = fmt.Sprintf(`
+
+
+
WhatsApp
+ %s
+
+
%s
+
+
`, statusPill, htmlEscape(client.WhatsAppNumber), client.ClientID, map[bool]string{true: "Reconnect", false: "Reconnect"}[true])
} else {
- whatsappSection = `
-
WhatsApp: Not configured Connect
`
+ waSection = fmt.Sprintf(`
+
+
WhatsApp
+
No WhatsApp number configured for this client.
+
Connect WhatsApp
+
`, client.ClientID)
}
- w.Write([]byte(`
-
-
-
-
` + client.Name + `
-
Client ID: ` + strconv.FormatInt(client.ClientID, 10) + `
-
Phone: ` + client.Phone + `
-
Email: ` + client.Email + `
-
Address: ` + client.Address + `
-
Notes: ` + client.Notes + `
` + whatsappSection + `
-
Back
-`))
+ fmt.Fprintf(buf, `
+
+
+
+
Details
+
+
+ Phone
+ %s
+
+
+ Email
+ %s
+
+
+ Address
+ %s
+
+
+ Notes
+ %s
+
+
+
+
+
+ %s
+
+
+
`, htmlEscape(client.Phone), htmlEscape(client.Email), htmlEscape(client.Address), htmlEscape(client.Notes), waSection)
+
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+ templates.WritePage(w, buf, client.Name, "clients")
}
func (a *App) UpdateClient(w http.ResponseWriter, r *http.Request) {
@@ -217,7 +270,7 @@ func (a *App) DeleteClient(w http.ResponseWriter, r *http.Request) {
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
- w.Write([]byte("OK"))
+ w.Write([]byte(""))
}
// --- package-level shims kept for existing tests ---
diff --git a/apps/go-crm/internal/handlers/customers.go b/apps/go-crm/internal/handlers/customers.go
index d91725f..7c94fac 100644
--- a/apps/go-crm/internal/handlers/customers.go
+++ b/apps/go-crm/internal/handlers/customers.go
@@ -1,12 +1,14 @@
package handlers
import (
+ "fmt"
"net/http"
"strconv"
"time"
"go-crm/config"
"go-crm/internal/db"
+ "go-crm/internal/templates"
"github.com/go-chi/chi/v5"
)
@@ -38,15 +40,76 @@ func (a *App) ListCustomers(w http.ResponseWriter, r *http.Request) {
var clientOptions string
for _, c := range clients {
- clientOptions += `
`
+ clientOptions += fmt.Sprintf(`
`, c.ClientID, htmlEscape(c.Name))
+ }
+
+ buf := templates.BufRender()
+ actions := `
`
+ fmt.Fprint(buf, templates.PageHeader("Customers", "Your customer database", actions))
+
+ fmt.Fprintf(buf, `
+
`, clientOptions)
+
+ if len(customers) == 0 {
+ fmt.Fprint(buf, templates.EmptyState(`
`, "No customers found. Add your first customer."))
+ } else {
+ fmt.Fprintf(buf, `
`)
+ fmt.Fprint(buf, templates.TableStart([]string{"Name", "Phone", "Birth Date", "Instagram", "Actions"}))
+ for _, c := range customers {
+ fmt.Fprintf(buf, `
+ | %s |
+ %s |
+ %s |
+ %s |
+
+
+ View
+
+
+
+ |
+
`, htmlEscape(c.Name), htmlEscape(c.Phone), htmlEscape(c.BirthDate), htmlEscape(c.Instagram), c.CustomerID, c.CustomerID, c.CustomerID)
+ }
+ fmt.Fprint(buf, templates.TableEnd())
+ fmt.Fprintf(buf, `
`)
+
+ for _, c := range customers {
+ fmt.Fprintf(buf, `
+
`, c.CustomerID, htmlEscape(c.Name), c.CustomerID, htmlEscape(c.Name), htmlEscape(c.Phone), htmlEscape(c.BirthDate), htmlEscape(c.Instagram), c.ClientID, htmlEscape(c.Name), clientOptions, c.CustomerID)
+ }
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
- w.Write([]byte(`
Customers
`))
+ templates.WritePage(w, buf, "Customers", "customers")
}
func (a *App) CreateCustomer(w http.ResponseWriter, r *http.Request) {
@@ -134,8 +197,33 @@ func (a *App) ViewCustomer(w http.ResponseWriter, r *http.Request) {
return
}
+ buf := templates.BufRender()
+ fmt.Fprint(buf, templates.PageHeader(htmlEscape(c.Name), "Customer profile"))
+ fmt.Fprintf(buf, `
+
+
+
+
+ Phone
+ %s
+
+
+ Birth Date
+ %s
+
+
+ Instagram
+ %s
+
+
+
+
+
`, htmlEscape(c.Phone), htmlEscape(c.BirthDate), htmlEscape(c.Instagram))
+
w.Header().Set("Content-Type", "text/html; charset=utf-8")
- w.Write([]byte(`
` + c.Name + `
Phone: ` + c.Phone + `
Birth Date: ` + c.BirthDate + `
Instagram: ` + c.Instagram + `
Back