package ui import ( "encoding/json" "html/template" "io" "strings" "time" "github.com/hireme/hireme/internal/db" ) var funcMap = template.FuncMap{ "formatTime": func(t *time.Time) string { if t == nil { return "Never" } return t.Format("Jan 02, 2006 15:04") }, "formatDate": func(t time.Time) string { return t.Format("Jan 02, 2006") }, "parseJSON": func(jsonStr string) []string { if jsonStr == "" { return nil } var list []string _ = json.Unmarshal([]byte(jsonStr), &list) return list }, "scoreBadgeClass": func(score float64) string { if score >= 50 { return "bg-emerald-500/20 text-emerald-400 border-emerald-500/30" } else if score >= 20 { return "bg-amber-500/20 text-amber-400 border-amber-500/30" } return "bg-slate-700/50 text-slate-400 border-slate-600/30" }, "platformIcon": func(platform string) string { p := strings.ToLower(platform) if strings.Contains(p, "linkedin") { return "💼 LinkedIn" } else if strings.Contains(p, "google") { return "🔍 Google Jobs" } else if strings.Contains(p, "greenhouse") { return "🌿 Greenhouse" } else if strings.Contains(p, "lever") { return "⚙️ Lever" } return "🌐 Web Target" }, } type PageData struct { Targets []db.SearchTarget Jobs []db.JobWithAnalysis Applications []db.JobWithAnalysis ActiveTab string LLMProvider string QualifiedJobsCount int TotalIngestedCount int ActiveApplications int } var ( rootTemplate *template.Template ) func init() { var err error rootTemplate = template.New("root").Funcs(funcMap) rootTemplate, err = rootTemplate.Parse(mainLayoutHTML) if err != nil { panic(err) } rootTemplate, err = rootTemplate.Parse(targetListHTML) if err != nil { panic(err) } rootTemplate, err = rootTemplate.Parse(jobListHTML) if err != nil { panic(err) } rootTemplate, err = rootTemplate.Parse(jobDetailHTML) if err != nil { panic(err) } rootTemplate, err = rootTemplate.Parse(kanbanBoardHTML) if err != nil { panic(err) } rootTemplate, err = rootTemplate.Parse(kanbanCardHTML) if err != nil { panic(err) } } func RenderPage(w io.Writer, data PageData) error { return rootTemplate.ExecuteTemplate(w, "page", data) } func RenderTargetList(w io.Writer, targets []db.SearchTarget) error { return rootTemplate.ExecuteTemplate(w, "targets", targets) } func RenderJobList(w io.Writer, jobs []db.JobWithAnalysis) error { return rootTemplate.ExecuteTemplate(w, "jobs", jobs) } func RenderJobDetail(w io.Writer, job *db.JobWithAnalysis) error { return rootTemplate.ExecuteTemplate(w, "jobDetail", job) } func RenderKanbanBoard(w io.Writer, apps []db.JobWithAnalysis) error { return rootTemplate.ExecuteTemplate(w, "kanban", apps) } const mainLayoutHTML = `{{ define "page" }} Hireme | Job Command Center
H

Hireme

Job Application Command Center

Total Search Targets

{{ len .Targets }}

Active Crawler Pipelines

Qualified Jobs Matched

{{ .QualifiedJobsCount }}

Passed Keyword & Score Filter

Total Ingested Postings

{{ .TotalIngestedCount }}

Deduplicated Raw Crawls

Active Applications

{{ .ActiveApplications }}

Tracked in Kanban Pipeline

Qualified Job Opportunities

Ranked by rule score + cached LLM skill gap analysis

{{ template "jobs" .Jobs }}
{{ end }}` const targetListHTML = `{{ define "targets" }}
{{ range . }} {{ else }} {{ end }}
Platform Name Target URL Last Crawled Status Action
{{ platformIcon .Platform }} {{ .Name }} {{ .TargetURL }} {{ formatTime .LastCrawledAt }} {{ if .IsActive }} Active {{ else }} Paused {{ end }}
No search targets configured yet.
{{ end }}` const jobListHTML = `{{ define "jobs" }}
{{ range . }} {{ else }} {{ end }}
Job Title & Company Original Link Analysis Status Score & Qualification Keyword Tags Crawled Date Action
{{ .Company }} • 📍 {{ .Location }}
🔗 {{ if .Analysis }} {{ if eq .Analysis.Status "PARSED" }} PARSED {{ else if eq .Analysis.Status "FILTERED_OUT" }} FILTERED_OUT {{ else }} {{ .Analysis.Status }} {{ end }} {{ else }} UNANALYZED {{ end }} {{ if .Analysis }} {{ if .Analysis.IsQualified }} ★ {{ printf "%.0f" .Analysis.Score }} Qualified {{ else }} Not Qualified {{ end }} {{ else }} - {{ end }} {{ if .Analysis }}
{{ range parseJSON .Analysis.MatchedHighlights }} {{ . }} {{ else }} No tags {{ end }}
{{ else }} No tags {{ end }}
{{ formatDate .CrawledAt }} {{ if .App }} {{ .App.Status }} {{ else }} {{ end }}
No job postings found yet. Click ⚡ Run Pipeline to ingest jobs.
{{ end }}` const jobDetailHTML = `{{ define "jobDetail" }}
{{ .Company }}

{{ .Title }}

📍 {{ .Location }} • Ingested {{ formatDate .CrawledAt }}

{{ if .Analysis }}
Match Relevance Score {{ printf "%.1f" .Analysis.Score }} / 100
LLM Status {{ .Analysis.Status }}

✓ Matched Technical Highlights

{{ range parseJSON .Analysis.MatchedHighlights }} {{ . }} {{ end }}

! Skill & Experience Gaps

{{ range parseJSON .Analysis.MissingHighlights }} {{ . }} {{ end }}

💡 Resume Tailoring Action Points

    {{ range parseJSON .Analysis.TailoringSuggestions }}
  • • {{ . }}
  • {{ end }}
{{ end }}

Job Description Payload

{{ .RawContent }}
Apply External ↗
{{ end }}` const kanbanBoardHTML = `{{ define "kanban" }}

💾 Saved

{{ range . }} {{ if eq .App.Status "SAVED" }} {{ template "kanbanCard" . }} {{ end }} {{ end }}

🚀 Applied

{{ range . }} {{ if eq .App.Status "APPLIED" }} {{ template "kanbanCard" . }} {{ end }} {{ end }}

💬 Interview

{{ range . }} {{ if eq .App.Status "INTERVIEW" }} {{ template "kanbanCard" . }} {{ end }} {{ end }}

🎉 Offer / Closed

{{ range . }} {{ if or (eq .App.Status "OFFER") (eq .App.Status "REJECTED") }} {{ template "kanbanCard" . }} {{ end }} {{ end }}
{{ end }}` const kanbanCardHTML = ` {{ define "kanbanCard" }}
{{ .Company }}

{{ .Title }}

📍 {{ .Location }}
Status: {{ .App.Status }}
{{ end }}`