initial public release
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user