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, `
%s
%s
`, 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, `
%s %s
%s
%s
`, 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, `
Review Queue
%d pending
Monthly Report
Performance analytics
Clients
Manage your businesses
`, 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) }