178 lines
4.2 KiB
Go
178 lines
4.2 KiB
Go
package tui
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
|
|
"github.com/charmbracelet/bubbles/table"
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
"github.com/charmbracelet/lipgloss"
|
|
)
|
|
|
|
type ReportsModel struct {
|
|
db *sql.DB
|
|
table table.Model
|
|
rows []reportRow
|
|
status string
|
|
width int
|
|
height int
|
|
}
|
|
|
|
type reportRow struct {
|
|
ID int
|
|
Seq int
|
|
Company string
|
|
Title string
|
|
Score float64
|
|
Legitimacy string
|
|
FilePath string
|
|
CreatedAt string
|
|
}
|
|
|
|
func NewReportsModel(db *sql.DB) *ReportsModel {
|
|
m := &ReportsModel{db: db, status: "[r] refresh [enter] open report"}
|
|
m.load()
|
|
m.table = newReportsTable(m.rows, 0, 0)
|
|
return m
|
|
}
|
|
|
|
func (m *ReportsModel) load() {
|
|
m.rows = nil
|
|
if m.db == nil {
|
|
m.status = "no database"
|
|
return
|
|
}
|
|
rows, err := m.db.Query(`
|
|
SELECT r.id, COALESCE(r.seq,0), COALESCE(j.company,''), COALESCE(j.title,''),
|
|
COALESCE(r.score, 0), COALESCE(r.legitimacy, ''), COALESCE(r.file_path, ''),
|
|
COALESCE(r.created_at, '')
|
|
FROM reports r
|
|
LEFT JOIN applications a ON a.id = r.application_id
|
|
LEFT JOIN jobs j ON j.id = a.job_id
|
|
ORDER BY r.id DESC
|
|
LIMIT 200
|
|
`)
|
|
if err != nil {
|
|
m.status = fmt.Sprintf("query failed: %v", err)
|
|
return
|
|
}
|
|
defer rows.Close()
|
|
for rows.Next() {
|
|
var r reportRow
|
|
if err := rows.Scan(&r.ID, &r.Seq, &r.Company, &r.Title, &r.Score, &r.Legitimacy, &r.FilePath, &r.CreatedAt); err != nil {
|
|
continue
|
|
}
|
|
m.rows = append(m.rows, r)
|
|
}
|
|
}
|
|
|
|
func reportsColumns() []table.Column {
|
|
return []table.Column{
|
|
{Title: "#", Width: 5},
|
|
{Title: "Company", Width: 22},
|
|
{Title: "Title", Width: 30},
|
|
{Title: "Score", Width: 6},
|
|
{Title: "Legit", Width: 10},
|
|
{Title: "Created", Width: 19},
|
|
}
|
|
}
|
|
|
|
func newReportsTable(rows []reportRow, w, h int) table.Model {
|
|
cols := flexColumns(w, reportsColumns(), []int{1, 2})
|
|
trows := make([]table.Row, 0, len(rows))
|
|
for _, r := range rows {
|
|
trows = append(trows, reportRowToTable(r))
|
|
}
|
|
t := table.New(
|
|
table.WithColumns(cols),
|
|
table.WithRows(trows),
|
|
table.WithFocused(true),
|
|
table.WithHeight(tableHeight(h)),
|
|
)
|
|
s := table.DefaultStyles()
|
|
s.Header = s.Header.BorderStyle(lipgloss.NormalBorder()).
|
|
BorderForeground(ColorBorder).BorderBottom(true).Bold(true).Foreground(ColorAccentAlt)
|
|
s.Selected = s.Selected.Bold(true).Foreground(ColorText).Background(ColorHighlight)
|
|
t.SetStyles(s)
|
|
return t
|
|
}
|
|
|
|
func reportRowToTable(r reportRow) table.Row {
|
|
seq := "-"
|
|
if r.Seq > 0 {
|
|
seq = fmt.Sprintf("#%03d", r.Seq)
|
|
}
|
|
score := "-"
|
|
if r.Score > 0 {
|
|
score = fmt.Sprintf("%.1f", r.Score)
|
|
}
|
|
return table.Row{seq, r.Company, r.Title, score, r.Legitimacy, r.CreatedAt}
|
|
}
|
|
|
|
func (m *ReportsModel) Init() tea.Cmd { return nil }
|
|
|
|
func (m *ReportsModel) Resize(w, h int) {
|
|
m.width = w
|
|
m.height = h
|
|
m.table.SetHeight(tableHeight(h))
|
|
m.table.SetWidth(w - 2)
|
|
m.table.SetColumns(flexColumns(w-2, reportsColumns(), []int{1, 2}))
|
|
}
|
|
|
|
func (m *ReportsModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
switch msg := msg.(type) {
|
|
case tea.KeyMsg:
|
|
switch msg.String() {
|
|
case "r":
|
|
m.load()
|
|
m.refreshTable()
|
|
m.status = fmt.Sprintf("%d reports", len(m.rows))
|
|
case "enter":
|
|
if cur := m.table.Cursor(); cur < len(m.rows) {
|
|
path := m.rows[cur].FilePath
|
|
if path == "" {
|
|
m.status = "no file_path on this report"
|
|
return m, nil
|
|
}
|
|
if _, err := os.Stat(path); err != nil {
|
|
m.status = fmt.Sprintf("missing: %s", path)
|
|
return m, nil
|
|
}
|
|
m.openInPager(path)
|
|
}
|
|
}
|
|
}
|
|
var cmd tea.Cmd
|
|
m.table, cmd = m.table.Update(msg)
|
|
return m, cmd
|
|
}
|
|
|
|
func (m *ReportsModel) refreshTable() {
|
|
trows := make([]table.Row, 0, len(m.rows))
|
|
for _, r := range m.rows {
|
|
trows = append(trows, reportRowToTable(r))
|
|
}
|
|
m.table.SetRows(trows)
|
|
}
|
|
|
|
func (m *ReportsModel) openInPager(path string) {
|
|
pager := os.Getenv("PAGER")
|
|
if pager == "" {
|
|
pager = "less"
|
|
}
|
|
cmd := exec.Command(pager, path)
|
|
cmd.Stdin = os.Stdin
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
_ = cmd.Run()
|
|
}
|
|
|
|
func (m *ReportsModel) View() string {
|
|
header := StyleTitle.Render("Reports — evaluation history")
|
|
sub := StyleSubtitle.Render(fmt.Sprintf("%d reports · [r] refresh · [enter] open in $PAGER", len(m.rows)))
|
|
status := StyleStatusMuted.Render(m.status)
|
|
return lipgloss.JoinVertical(lipgloss.Left, header, sub, "", m.table.View(), "", status)
|
|
}
|