package templates
import (
"bytes"
"fmt"
"html/template"
"io"
"strings"
)
// Page renders a full HTML page with the industrial terminal layout.
type Page struct {
Title string
ActiveNav string
Content template.HTML
ExtraHead template.HTML
}
// NavItem defines a navigation entry.
type NavItem struct {
Label string
Href string
Icon string
ID string
}
var navItems = []NavItem{
{Label: "Dashboard", Href: "/", Icon: "M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6", ID: "dashboard"},
{Label: "Review Queue", Href: "/leads/review", Icon: "M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4", ID: "review"},
{Label: "All Leads", Href: "/leads/all", Icon: "M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0z", ID: "leads"},
{Label: "Report", Href: "/report", Icon: "M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6m10 0v-6a2 2 0 012-2h2a2 2 0 012 2v6m-6 0V7a2 2 0 012-2h2a2 2 0 012 2v12", ID: "report"},
{Label: "Clients", Href: "/clients", Icon: "M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5", ID: "clients"},
{Label: "Customers", Href: "/customers", Icon: "M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z", ID: "customers"},
{Label: "Services", Href: "/services", Icon: "M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37 1.608.536 3.135-.493 3.35-2.572zM15 12a3 3 0 11-6 0 3 3 0 016 0z", ID: "services"},
{Label: "Scheduling", Href: "/scheduling", Icon: "M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z", ID: "scheduling"},
{Label: "Payments", Href: "/payments", Icon: "M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3-.895 3-2-1.343-2-3-2zm0 0v1m0 0v1m0-1h1m-1 0H9m12 0a2 2 0 012 2v4.5a2.5 2.5 0 01-2.5 2.5h-15a2.5 2.5 0 01-2.5-2.5V10a2 2 0 012-2h15z", ID: "payments"},
{Label: "Questions", Href: "/questions", Icon: "M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z", ID: "questions"},
{Label: "Answers", Href: "/answers", Icon: "M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z", ID: "answers"},
}
const layoutTemplate = `
{{.Title}} — go-crm
{{.ExtraHead}}
`
// NavItemRender is used in the layout template.
type NavItemRender struct {
Label string
Href string
Icon string
Active bool
}
// RenderPage writes a complete HTML page with the industrial layout.
func RenderPage(w io.Writer, title, activeNav string, content template.HTML) error {
var items []NavItemRender
for _, ni := range navItems {
items = append(items, NavItemRender{
Label: ni.Label,
Href: ni.Href,
Icon: ni.Icon,
Active: ni.ID == activeNav,
})
}
tmpl, err := template.New("layout").Parse(layoutTemplate)
if err != nil {
return err
}
return tmpl.Execute(w, struct {
Title string
NavItems []NavItemRender
Content template.HTML
ExtraHead template.HTML
}{
Title: title,
NavItems: items,
Content: content,
})
}
// ---------------------------------------------------------------------------
// Page Sections / Helpers
// ---------------------------------------------------------------------------
// PageHeader generates a page header with title and optional action buttons.
func PageHeader(title, subtitle string, actions ...string) template.HTML {
var actionHTML string
if len(actions) > 0 {
actionHTML = `` + strings.Join(actions, "") + `
`
}
return template.HTML(fmt.Sprintf(`
`, template.HTMLEscapeString(title), template.HTMLEscapeString(subtitle), actionHTML))
}
// KPI card for dashboards.
func KPICard(label, value, trend, colorClass, iconSVG string, stagger int) template.HTML {
borderColor := "border-white/[0.06]"
textColor := "text-zinc-300"
switch colorClass {
case "amber":
borderColor = "border-amber-400/15"
textColor = "text-amber-400"
case "emerald":
borderColor = "border-emerald-400/15"
textColor = "text-emerald-400"
case "rose":
borderColor = "border-rose-400/15"
textColor = "text-rose-400"
case "sky":
borderColor = "border-sky-400/15"
textColor = "text-sky-400"
}
trendHTML := ""
if trend != "" {
trendHTML = fmt.Sprintf(`%s `, template.HTMLEscapeString(trend))
}
return template.HTML(fmt.Sprintf(`
`, borderColor, stagger,
template.HTMLEscapeString(label),
textColor,
template.HTMLEscapeString(value),
trendHTML,
textColor,
iconSVG))
}
// TableStart begins a data table.
func TableStart(headers []string) template.HTML {
var ths string
for _, h := range headers {
ths += fmt.Sprintf(`%s `, template.HTMLEscapeString(h))
}
return template.HTML(fmt.Sprintf(`%s `, ths))
}
// TableEnd closes a data table.
func TableEnd() template.HTML {
return template.HTML(`
`)
}
// EmptyState shows when no data exists.
func EmptyState(icon, message string) template.HTML {
return template.HTML(fmt.Sprintf(`
`, icon, template.HTMLEscapeString(message)))
}
// SectionCard wraps content in a card with title.
func SectionCard(title, subtitle string, content template.HTML) template.HTML {
return template.HTML(fmt.Sprintf(`
`, template.HTMLEscapeString(title), template.HTMLEscapeString(subtitle), content))
}
// ---------------------------------------------------------------------------
// Render helpers that write to a buffer then to a ResponseWriter
// ---------------------------------------------------------------------------
// BufRender starts a bytes.Buffer for collecting HTML content.
func BufRender() *bytes.Buffer {
return &bytes.Buffer{}
}
// WritePage renders the full page from a buffer to a ResponseWriter.
func WritePage(w io.Writer, buf *bytes.Buffer, title, activeNav string) error {
return RenderPage(w, title, activeNav, template.HTML(buf.String()))
}
// ---------------------------------------------------------------------------
// Convenience: write raw HTML directly to ResponseWriter with layout
// ---------------------------------------------------------------------------
// WriteHTMLPage takes an io.Writer, writes the layout wrapping the given HTML string.
func WriteHTMLPage(w io.Writer, title, activeNav, html string) error {
return RenderPage(w, title, activeNav, template.HTML(html))
}