diff --git a/internal/db/queries.go b/internal/db/queries.go index af99855..5328b8a 100644 --- a/internal/db/queries.go +++ b/internal/db/queries.go @@ -124,13 +124,30 @@ func AddJobReport(j Job, status string, encodedSize int64, sizeSaved int64, proc return err } -func GetJobReports(limit, offset int) ([]JobReport, int, error) { +func GetJobReports(limit, offset int, statusFilter string) ([]JobReport, int, error) { var total int - if err := DB.QueryRow(`SELECT COUNT(*) FROM job_reports`).Scan(&total); err != nil { + queryArgs := []interface{}{} + countQuery := `SELECT COUNT(*) FROM job_reports` + selectQuery := `SELECT id, file_path, media_type, status, original_size, encoded_size, size_saved, processing_time, target_resolution, ffmpeg_flags, error_message, created_at FROM job_reports` + whereClause := "" + + if statusFilter != "" && statusFilter != "all" { + if statusFilter == "exclude_skipped" { + whereClause = ` WHERE status != 'skipped'` + } else { + whereClause = ` WHERE status = ?` + queryArgs = append(queryArgs, statusFilter) + } + } + + if err := DB.QueryRow(countQuery+whereClause, queryArgs...).Scan(&total); err != nil { return nil, 0, err } - rows, err := DB.Query(`SELECT id, file_path, media_type, status, original_size, encoded_size, size_saved, processing_time, target_resolution, ffmpeg_flags, error_message, created_at FROM job_reports ORDER BY created_at DESC LIMIT ? OFFSET ?`, limit, offset) + selectQuery += whereClause + ` ORDER BY created_at DESC LIMIT ? OFFSET ?` + queryArgs = append(queryArgs, limit, offset) + + rows, err := DB.Query(selectQuery, queryArgs...) if err != nil { return nil, 0, err } diff --git a/internal/web/server.go b/internal/web/server.go index 9d46c52..ac75c38 100644 --- a/internal/web/server.go +++ b/internal/web/server.go @@ -9,6 +9,7 @@ import ( "log" "net/http" "path/filepath" + "strconv" "goencode/internal/config" "goencode/internal/db" @@ -254,20 +255,57 @@ func (s *Server) handlePage(tmplName string) http.HandlerFunc { } func (s *Server) handleHistory(w http.ResponseWriter, r *http.Request) { - reports, total, err := db.GetJobReports(50, 0) // Basic pagination for now + limitStr := r.URL.Query().Get("limit") + pageStr := r.URL.Query().Get("page") + statusFilter := r.URL.Query().Get("status") + + limit := 50 + if l, err := strconv.Atoi(limitStr); err == nil && l > 0 { + limit = l + } + + page := 1 + if p, err := strconv.Atoi(pageStr); err == nil && p > 0 { + page = p + } + + offset := (page - 1) * limit + + reports, total, err := db.GetJobReports(limit, offset, statusFilter) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } + totalPages := (total + limit - 1) / limit + if totalPages == 0 { + totalPages = 1 + } + data := struct { - AuthEnabled bool - Reports []db.JobReport - Total int + AuthEnabled bool + Reports []db.JobReport + Total int + CurrentPage int + TotalPages int + HasNext bool + HasPrev bool + PrevPage int + NextPage int + FilterStatus string + Limit int }{ - AuthEnabled: s.cfg.Auth.Username != "", - Reports: reports, - Total: total, + AuthEnabled: s.cfg.Auth.Username != "", + Reports: reports, + Total: total, + CurrentPage: page, + TotalPages: totalPages, + HasNext: page < totalPages, + HasPrev: page > 1, + PrevPage: page - 1, + NextPage: page + 1, + FilterStatus: statusFilter, + Limit: limit, } tmpl, err := template.New("layout").Funcs(template.FuncMap{ diff --git a/web/static/style.css b/web/static/style.css index ae419f9..87352e0 100644 --- a/web/static/style.css +++ b/web/static/style.css @@ -59,6 +59,14 @@ a { color: var(--text-secondary); font-weight: 500; transition: color 0.2s; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.nav-links a svg { + width: 1.25rem; + height: 1.25rem; } .nav-links a:hover, .nav-links a.active { @@ -216,6 +224,65 @@ tbody tr:hover { border-color: var(--accent); } +/* Pagination */ +.pagination { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 1rem; + padding-top: 1rem; + border-top: 1px solid var(--border); +} + +.pagination-controls { + display: flex; + gap: 0.5rem; +} + +.page-btn { + background: var(--surface); + color: var(--text-primary); + border: 1px solid var(--border); + padding: 0.5rem 1rem; + border-radius: 4px; + cursor: pointer; + text-decoration: none; + font-size: 0.875rem; + transition: all 0.2s; +} + +.page-btn:hover { + background: var(--surface-hover); + border-color: var(--text-secondary); +} + +.page-btn.disabled { + opacity: 0.5; + cursor: not-allowed; + pointer-events: none; +} + +/* Filter Bar */ +.filter-bar { + display: flex; + gap: 0.5rem; + align-items: center; +} + +.filter-select { + padding: 0.5rem; + background: var(--bg); + border: 1px solid var(--border); + border-radius: 4px; + color: var(--text-primary); + font-size: 0.875rem; + outline: none; +} + +.filter-select:focus { + border-color: var(--accent); +} + /* Responsive adjustments */ @media (max-width: 768px) { .navbar { diff --git a/web/templates/history.html b/web/templates/history.html index 3732839..0b401eb 100644 --- a/web/templates/history.html +++ b/web/templates/history.html @@ -2,8 +2,17 @@