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}}

{{.Title}}

{{.Content}}
` // 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(`

%s

%s

%s
`, 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(`
%s
%s
%s
%s
`, 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(`
%s

%s

`, 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(`

%s

%s

%s
`, 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)) }