package handlers
import (
"fmt"
"html/template"
"net/http"
"time"
"go-crm/internal/db"
"go-crm/internal/templates"
)
func (a *App) Dashboard(w http.ResponseWriter, r *http.Request) {
accountID, err := a.getSession(r)
if err != nil {
http.Redirect(w, r, "/auth/login", http.StatusFound)
return
}
var clientID int64
a.DB.QueryRow("SELECT client_id FROM clients WHERE account_id = ? LIMIT 1", accountID).Scan(&clientID)
// Gather metrics
var reviewCount int
var totalLeads int
var totalScheduled int
var totalRevenue float64
var totalSales int
var totalCustomers int
var totalServices int
var pendingSchedules int
if clientID > 0 {
a.DB.QueryRow("SELECT COUNT(*) FROM leads WHERE client_id = ? AND needs_review = 1", clientID).Scan(&reviewCount)
a.DB.QueryRow("SELECT COUNT(*) FROM leads WHERE client_id = ?", clientID).Scan(&totalLeads)
a.DB.QueryRow("SELECT COUNT(*) FROM leads WHERE client_id = ? AND status = 'Agendou'", clientID).Scan(&totalScheduled)
a.DB.QueryRow("SELECT COALESCE(SUM(amount),0), COUNT(*) FROM payments WHERE client_id = ? AND has_paid = 1", clientID).Scan(&totalRevenue, &totalSales)
a.DB.QueryRow("SELECT COUNT(*) FROM customers WHERE client_id IN (SELECT client_id FROM clients WHERE account_id = ?)", accountID).Scan(&totalCustomers)
a.DB.QueryRow("SELECT COUNT(*) FROM services WHERE client_id = ?", clientID).Scan(&totalServices)
a.DB.QueryRow("SELECT COUNT(*) FROM scheduling WHERE client_id = ? AND status = 'pending'", clientID).Scan(&pendingSchedules)
}
// WhatsApp status
waConnected := false
waPhone := ""
if a.WAConnector != nil && clientID > 0 {
if connected, _ := a.WAConnector.IsConnected(r.Context(), clientID); connected {
waConnected = true
} else {
var dbConnected int
a.DB.QueryRow("SELECT whatsapp_connected FROM clients WHERE client_id = ?", clientID).Scan(&dbConnected)
if dbConnected == 1 {
waConnected = true
}
}
var waNum string
a.DB.QueryRow("SELECT COALESCE(whatsapp_number,'') FROM clients WHERE client_id = ?", clientID).Scan(&waNum)
waPhone = waNum
}
// Recent leads
var recentLeads []db.Lead
if clientID > 0 {
leads, _ := db.ListAllLeads(a.DB, clientID, 5, 0)
recentLeads = leads
}
// Account name
var accountName string
a.DB.QueryRow("SELECT COALESCE(name,'User') FROM accounts WHERE account_id = ?", accountID).Scan(&accountName)
buf := templates.BufRender()
// KPI Row
fmt.Fprintf(buf, `
`)
fmt.Fprint(buf, templates.KPICard("Total Leads", fmt.Sprintf("%d", totalLeads), "", "amber", `
`, 1))
fmt.Fprint(buf, templates.KPICard("Agendamentos", fmt.Sprintf("%d", totalScheduled), "", "emerald", `
`, 2))
fmt.Fprint(buf, templates.KPICard("Revenue", fmt.Sprintf("R$ %.2f", totalRevenue), fmt.Sprintf("%d sales", totalSales), "sky", `
`, 3))
fmt.Fprint(buf, templates.KPICard("Pending Review", fmt.Sprintf("%d", reviewCount), "", "rose", `
`, 4))
fmt.Fprintf(buf, `
`)
// Secondary KPIs
fmt.Fprintf(buf, ``)
secondaryKPIs := []struct{ label, value string }{
{"Customers", fmt.Sprintf("%d", totalCustomers)},
{"Services", fmt.Sprintf("%d", totalServices)},
{"Pending Schedules", fmt.Sprintf("%d", pendingSchedules)},
{"Conversion Rate", fmt.Sprintf("%.1f%%", conversionRate(totalScheduled, totalLeads))},
}
for _, kpi := range secondaryKPIs {
fmt.Fprintf(buf, `
`, kpi.label, kpi.value)
}
fmt.Fprintf(buf, `
`)
// WhatsApp Status Card
waIcon := ``
waText := "Disconnected"
waSub := "No active session"
waAction := fmt.Sprintf(`Connect`, clientID)
if waConnected {
waIcon = ``
waText = "Connected"
waSub = waPhone
waAction = `Online`
} else if clientID == 0 {
waAction = `Create Client`
}
if waPhone == "" && clientID > 0 {
waSub = "Phone not configured"
}
fmt.Fprintf(buf, `
`, waIcon, waText, waSub, waAction)
// Recent Leads Section
var recentHTML string
if len(recentLeads) == 0 {
recentHTML = string(templates.EmptyState(``, "No leads yet. Connect WhatsApp to start capturing."))
} else {
recentHTML = string(templates.TableStart([]string{"Phone", "Name", "Service", "Status", "Arrived"}))
for _, l := range recentLeads {
statusPill := statusPillHTML(l.Status)
reviewBadge := ""
if l.NeedsReview {
reviewBadge = `review`
}
arrived := time.Unix(l.CreatedAt, 0).Format("02/01 15:04")
recentHTML += fmt.Sprintf(`
| %s |
%s%s |
%s |
%s |
%s |
`, htmlEscape(l.PhoneNormalized), htmlEscape(l.Name), reviewBadge, htmlEscape(l.ServiceInterest), statusPill, arrived)
}
recentHTML += string(templates.TableEnd())
}
fmt.Fprint(buf, templates.SectionCard("Recent Leads", "Last 5 captured entries", template.HTML(recentHTML)))
// Quick actions
fmt.Fprintf(buf, `
`, reviewCount)
w.Header().Set("Content-Type", "text/html; charset=utf-8")
templates.WritePage(w, buf, "Dashboard", "dashboard")
}
func conversionRate(scheduled, total int) float64 {
if total == 0 {
return 0
}
return float64(scheduled) / float64(total) * 100
}
func statusPillHTML(status string) string {
color := "pill-zinc"
switch status {
case "Agendou":
color = "pill-emerald"
case "Cancelou":
color = "pill-rose"
case "Converteu":
color = "pill-sky"
case "Em negociacao":
color = "pill-amber"
}
return fmt.Sprintf(`%s`, color, htmlEscape(status))
}
// Dashboard is the package-level shim.
func Dashboard(w http.ResponseWriter, r *http.Request) {
(&App{DB: DB, WAConnector: WAConnector}).Dashboard(w, r)
}