Browse Source

chore: style updates for document uploads

document-upload-removal-layout-update
nic 5 months ago
parent
commit
1af2a8af2a
  1. 177
      internal/handlers/web/documents.go
  2. 28
      static/css/styles.css
  3. 162
      static/css/upload.css
  4. 92
      templates/partials/upload_result_card.html
  5. 7
      templates/partials/upload_results_pagination.html
  6. 14
      templates/partials/upload_stats.html

177
internal/handlers/web/documents.go

@ -586,7 +586,7 @@ func UploadDocumentsHandler(w http.ResponseWriter, r *http.Request) {
} }
// Return first page of results with configurable page size // Return first page of results with configurable page size
renderUploadResultsPage(w, sessionID, utils.DefaultPage, limit) renderUploadResultsPage(w, sessionID, utils.DefaultPage, limit, "all")
} }
// UploadResultsHandler handles pagination for upload results // UploadResultsHandler handles pagination for upload results
@ -607,18 +607,93 @@ func UploadResultsHandler(w http.ResponseWriter, r *http.Request) {
limit = utils.DefaultPageSize limit = utils.DefaultPageSize
} }
renderUploadResultsPage(w, sessionID, page, limit) // Optional filter: all|success|failed
filter := strings.ToLower(strings.TrimSpace(r.URL.Query().Get("filter")))
if filter != "success" && filter != "failed" {
filter = "all"
}
renderUploadResultsPage(w, sessionID, page, limit, filter)
} }
// renderUploadResultsPage renders a paginated page of upload results // renderUploadResultsPage renders a paginated page of upload results
func renderUploadResultsPage(w http.ResponseWriter, sessionID string, page, limit int) { func renderUploadResultsPage(w http.ResponseWriter, sessionID string, page, limit int, filter string) {
uploadSession, exists := uploadSessions[sessionID] uploadSession, exists := uploadSessions[sessionID]
if !exists { if !exists {
http.Error(w, "Upload session not found", http.StatusNotFound) http.Error(w, "Upload session not found", http.StatusNotFound)
return return
} }
totalResults := len(uploadSession.GroupedResults) // Build a filtered view per job at the file level
var filteredJobs []struct {
JobID string
FilesFound int
FilesUploaded int
Success bool
ErrorMsg string
Files []struct {
Name string
Success bool
Error string
FileSize int64
}
}
for _, jr := range uploadSession.GroupedResults {
// Filter files per job
var files []struct {
Name string
Success bool
Error string
FileSize int64
}
for _, f := range jr.Files {
if filter == "success" && !f.Success {
continue
}
if filter == "failed" && f.Success {
continue
}
files = append(files, f)
}
if len(files) == 0 {
// Skip jobs that have no files matching the filter
if filter != "all" {
continue
}
}
// Copy job with filtered files
copy := struct {
JobID string
FilesFound int
FilesUploaded int
Success bool
ErrorMsg string
Files []struct {
Name string
Success bool
Error string
FileSize int64
}
}{
JobID: jr.JobID,
FilesFound: jr.FilesFound,
FilesUploaded: jr.FilesUploaded,
Success: jr.Success,
ErrorMsg: jr.ErrorMsg,
Files: func() []struct {
Name string
Success bool
Error string
FileSize int64
} {
return files
}(),
}
filteredJobs = append(filteredJobs, copy)
}
totalResults := len(filteredJobs)
pagination := utils.CalculatePagination(totalResults, page, limit) pagination := utils.CalculatePagination(totalResults, page, limit)
// Get results for this page // Get results for this page
@ -627,21 +702,25 @@ func renderUploadResultsPage(w http.ResponseWriter, sessionID string, page, limi
if endIndex > totalResults { if endIndex > totalResults {
endIndex = totalResults endIndex = totalResults
} }
pageResults := utils.GetPageResults(uploadSession.GroupedResults, startIndex, endIndex) pageResults := utils.GetPageResults(filteredJobs, startIndex, endIndex)
// Add pagination info to each job result for the template // Add pagination info to each job result for the template
var resultsWithPagination []map[string]interface{} var resultsWithPagination []map[string]interface{}
for _, jobResult := range pageResults { for _, jobResult := range pageResults {
filesLen := len(jobResult.Files)
displaySuccess := (filter == "success") || (filter != "failed" && jobResult.Success)
resultMap := map[string]interface{}{ resultMap := map[string]interface{}{
"JobID": jobResult.JobID, "JobID": jobResult.JobID,
"FilesFound": jobResult.FilesFound, "FilesFound": jobResult.FilesFound,
"FilesUploaded": jobResult.FilesUploaded, "FilesUploaded": jobResult.FilesUploaded,
"Success": jobResult.Success, "Success": jobResult.Success,
"ErrorMsg": jobResult.ErrorMsg, "ErrorMsg": jobResult.ErrorMsg,
"Files": jobResult.Files, "Files": jobResult.Files,
"FilePage": 1, // Default to first file "FilePage": 1, // Default to first file
"TotalFiles": len(jobResult.Files), "TotalFiles": filesLen,
"SessionID": sessionID, "SessionID": sessionID,
"Filter": filter,
"DisplaySuccess": displaySuccess,
} }
resultsWithPagination = append(resultsWithPagination, resultMap) resultsWithPagination = append(resultsWithPagination, resultMap)
} }
@ -662,13 +741,12 @@ func renderUploadResultsPage(w http.ResponseWriter, sessionID string, page, limi
"StartPage": pagination.StartPage, "StartPage": pagination.StartPage,
"EndPage": pagination.EndPage, "EndPage": pagination.EndPage,
"SessionID": sessionID, "SessionID": sessionID,
"Filter": filter,
} }
tmpl := root.WebTemplates tmpl := root.WebTemplates
if err := tmpl.ExecuteTemplate(w, "upload_results_pagination", data); err != nil { if err := tmpl.ExecuteTemplate(w, "upload_results_pagination", data); err != nil {
log.Printf("Template execution error: %v", err) log.Printf("Template execution error: %v", err)
// Don't call http.Error here as the response may have already started
// Just log the error and return
return return
} }
} }
@ -692,7 +770,6 @@ func (r *readCloserWithSize) Close() error {
return nil // Allow closing nil reader safely return nil // Allow closing nil reader safely
} }
// Size returns the current size of data read
func (r *readCloserWithSize) Size() int64 { func (r *readCloserWithSize) Size() int64 {
return r.size return r.size
} }
@ -711,6 +788,10 @@ func UploadJobFileHandler(w http.ResponseWriter, r *http.Request) {
filePage = parsed filePage = parsed
} }
} }
filter := strings.ToLower(strings.TrimSpace(r.URL.Query().Get("filter")))
if filter != "success" && filter != "failed" {
filter = "all"
}
uploadSession, exists := uploadSessions[sessionID] uploadSession, exists := uploadSessions[sessionID]
if !exists { if !exists {
@ -743,19 +824,37 @@ func UploadJobFileHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
totalFiles := len(jobResult.Files) // Apply file-level filter
var filteredFiles []struct {
Name string
Success bool
Error string
FileSize int64
}
for _, f := range jobResult.Files {
if filter == "success" && !f.Success {
continue
}
if filter == "failed" && f.Success {
continue
}
filteredFiles = append(filteredFiles, f)
}
totalFiles := len(filteredFiles)
if totalFiles == 0 { if totalFiles == 0 {
// No files to show
data := map[string]interface{}{ data := map[string]interface{}{
"JobID": jobResult.JobID, "JobID": jobResult.JobID,
"FilesFound": jobResult.FilesFound, "FilesFound": jobResult.FilesFound,
"FilesUploaded": jobResult.FilesUploaded, "FilesUploaded": jobResult.FilesUploaded,
"Success": jobResult.Success, "Success": jobResult.Success,
"ErrorMsg": jobResult.ErrorMsg, "ErrorMsg": jobResult.ErrorMsg,
"Files": nil, "Files": nil,
"FilePage": 1, "FilePage": 1,
"TotalFiles": 0, "TotalFiles": 0,
"SessionID": sessionID, "SessionID": sessionID,
"Filter": filter,
"DisplaySuccess": (filter == "success") || (filter != "failed" && jobResult.Success),
} }
tmpl := root.WebTemplates tmpl := root.WebTemplates
@ -798,20 +897,22 @@ func UploadJobFileHandler(w http.ResponseWriter, r *http.Request) {
Success bool Success bool
Error string Error string
FileSize int64 FileSize int64
}{jobResult.Files[filePage-1]}, }{filteredFiles[filePage-1]},
} }
// Add pagination info for the template // Add pagination info for the template
data := map[string]interface{}{ data := map[string]interface{}{
"JobID": jobResultCopy.JobID, "JobID": jobResultCopy.JobID,
"FilesFound": jobResultCopy.FilesFound, "FilesFound": jobResultCopy.FilesFound,
"FilesUploaded": jobResultCopy.FilesUploaded, "FilesUploaded": jobResultCopy.FilesUploaded,
"Success": jobResultCopy.Success, "Success": jobResultCopy.Success,
"ErrorMsg": jobResultCopy.ErrorMsg, "ErrorMsg": jobResultCopy.ErrorMsg,
"Files": jobResultCopy.Files, "Files": jobResultCopy.Files,
"FilePage": filePage, "FilePage": filePage,
"TotalFiles": totalFiles, "TotalFiles": totalFiles,
"SessionID": sessionID, "SessionID": sessionID,
"Filter": filter,
"DisplaySuccess": (filter == "success") || (filter != "failed" && jobResult.Success),
} }
tmpl := root.WebTemplates tmpl := root.WebTemplates

28
static/css/styles.css

@ -95,6 +95,9 @@ html {
color: var(--text-color); color: var(--text-color);
} }
/* Reserve vertical scrollbar space to prevent layout shift when content height changes */
/* Removed scrollbar-gutter reservation to avoid visual misalignment */
.flex { .flex {
display: flex; display: flex;
} }
@ -559,6 +562,10 @@ html {
.error::before { .error::before {
content: "⚠️"; content: "⚠️";
display: inline-block;
width: 1em;
/* reserve horizontal space so layout doesn't shift */
text-align: center;
} }
.not-found::before { .not-found::before {
@ -1736,9 +1743,10 @@ html {
/* Upload Result Cards */ /* Upload Result Cards */
.upload-results-container { .upload-results-container {
background-color: var(--dashboard-bg); background-color: var(--upload-card-bg);
border: var(--upload-card-border);
border-radius: 0.5rem; border-radius: 0.5rem;
box-shadow: var(--dashboard-shadow); box-shadow: var(--upload-card-shadow);
padding: 1.5rem; padding: 1.5rem;
margin-top: 1rem; margin-top: 1rem;
max-width: 100%; max-width: 100%;
@ -1763,7 +1771,8 @@ html {
} }
.stat-item { .stat-item {
background-color: rgba(30, 33, 43, 0.8); background-color: var(--upload-stat-bg);
border: var(--upload-stat-border);
border-radius: 0.5rem; border-radius: 0.5rem;
padding: 1.25rem; padding: 1.25rem;
text-align: center; text-align: center;
@ -1773,9 +1782,7 @@ html {
justify-content: center; justify-content: center;
} }
.stat-item:last-child { /* Do not force the last item into column 1; allow natural placement */
grid-column: 1;
}
.stat-value { .stat-value {
font-size: 2.5rem; font-size: 2.5rem;
@ -1800,13 +1807,14 @@ html {
.upload-results-grid { .upload-results-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 1rem; gap: 1rem;
margin-top: 1.5rem; margin-top: 1.5rem;
} }
.upload-result-card { .upload-result-card {
background-color: rgba(30, 33, 43, 0.8); background-color: var(--upload-card-bg);
border: var(--upload-card-border);
border-radius: 0.5rem; border-radius: 0.5rem;
padding: 1.25rem; padding: 1.25rem;
display: flex; display: flex;
@ -1833,6 +1841,10 @@ html {
padding: 0.25rem 0.75rem; padding: 0.25rem 0.75rem;
border-radius: 1rem; border-radius: 1rem;
font-size: 0.875rem; font-size: 0.875rem;
display: inline-flex;
align-items: center;
gap: 0.25rem;
white-space: nowrap;
} }
.upload-status.success { .upload-status.success {

162
static/css/upload.css

@ -288,21 +288,23 @@
/* Upload Results Container */ /* Upload Results Container */
.upload-results-container { .upload-results-container {
background-color: var(--upload-card-bg); background-color: var(--upload-card-bg);
border: var(--upload-card-border);
border-radius: 0.5rem; border-radius: 0.5rem;
box-shadow: var(--upload-card-shadow); box-shadow: var(--upload-card-shadow);
padding: 1.5rem; padding: 1.5rem;
margin-top: 1rem; margin-top: 1rem;
max-width: 100%; max-width: 100%;
width: 100%;
box-sizing: border-box;
overflow-x: hidden;
/* Revert: avoid forcing gutter here to prevent content width reduction */
} }
.upload-results-header { .upload-results-header {
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
} display: flex;
flex-direction: column;
.upload-results-header h3 { gap: 0.75rem;
color: var(--text-color);
font-size: 1.5rem;
margin: 0 0 1rem 0;
} }
/* Upload Stats Grid */ /* Upload Stats Grid */
@ -312,10 +314,16 @@
gap: 1rem; gap: 1rem;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
max-width: 600px; max-width: 600px;
width: 100%;
box-sizing: border-box;
padding: 0 0.5rem;
/* ensure visual breathing room on both sides */
} }
/* Stat item card styling (works in light and dark via CSS variables) */
.stat-item { .stat-item {
background-color: var(--upload-stat-bg); background-color: var(--upload-stat-bg);
border: var(--upload-stat-border);
border-radius: 0.5rem; border-radius: 0.5rem;
padding: 1.25rem; padding: 1.25rem;
text-align: center; text-align: center;
@ -326,10 +334,6 @@
min-width: 120px; min-width: 120px;
} }
.stat-item:last-child {
grid-column: 1;
}
.stat-value { .stat-value {
font-size: 2.5rem; font-size: 2.5rem;
font-weight: bold; font-weight: bold;
@ -344,94 +348,17 @@
text-align: center; text-align: center;
} }
/* Stat item variants */ /* Clickable stat items (success/failed) */
.success-stat .stat-value { .upload-stats .success-stat,
color: var(--upload-success-color); .upload-stats .error-stat,
} .upload-stats .all-stat {
cursor: pointer;
.error-stat .stat-value {
color: var(--upload-error-color);
}
.warning-stat .stat-value {
color: var(--upload-warning-color);
}
/* Upload Results Grid */
.upload-results-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1rem;
margin-top: 1.5rem;
}
.upload-result-card {
background-color: var(--upload-card-bg);
border-radius: 0.5rem;
padding: 1.25rem;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.upload-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
}
.upload-header h4 {
margin: 0;
color: var(--text-color);
font-size: 1rem;
font-weight: 500;
flex: 1;
}
.upload-status {
padding: 0.25rem 0.75rem;
border-radius: 1rem;
font-size: 0.875rem;
font-weight: 500;
}
.upload-status.success {
background-color: var(--upload-stat-success-bg);
color: var(--upload-success-color);
}
.upload-status.error {
background-color: var(--upload-stat-error-bg);
color: var(--upload-error-color);
}
.upload-details {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.upload-info {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.upload-info p {
margin: 0;
font-size: 0.875rem;
color: var(--content-text);
}
.success-text {
color: var(--upload-success-color);
font-weight: 500;
} }
.error-text { .upload-stats .success-stat:hover,
color: var(--upload-error-color); .upload-stats .error-stat:hover,
font-weight: 500; .upload-stats .all-stat:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, .12);
} }
/* Pagination Controls */ /* Pagination Controls */
@ -442,6 +369,7 @@
margin-top: 2rem; margin-top: 2rem;
padding-top: 1rem; padding-top: 1rem;
border-top: 1px solid var(--content-border); border-top: 1px solid var(--content-border);
overflow-x: auto;
} }
.pagination-info { .pagination-info {
@ -453,6 +381,35 @@
display: flex; display: flex;
gap: 0.5rem; gap: 0.5rem;
align-items: center; align-items: center;
flex-wrap: wrap;
}
/* Prevent long error text from blowing out cards */
.clamp-text {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 5;
overflow: hidden;
word-break: break-word;
white-space: pre-wrap;
position: relative;
}
/* show pointer and underline hint on hover */
.error-text.clamp-text,
.error-message.clamp-text {
cursor: pointer;
}
.error-text.clamp-text:hover,
.error-message.clamp-text:hover {
text-decoration: underline dotted;
}
/* When expanded, remove clamping */
.clamp-text.expanded {
-webkit-line-clamp: initial;
overflow: visible;
} }
.pagination-btn { .pagination-btn {
@ -565,9 +522,12 @@
border-color: rgba(34, 197, 94, 0.3); border-color: rgba(34, 197, 94, 0.3);
} }
/* Error bubble refinement */
.file-result.error { .file-result.error {
background-color: var(--upload-stat-error-bg); background-color: var(--upload-stat-error-bg);
border-color: rgba(239, 68, 68, 0.3); border-color: rgba(239, 68, 68, 0.3);
border-radius: 0.5rem;
/* rounder corners like old version */
} }
.status-icon { .status-icon {
@ -871,4 +831,14 @@
width: 100%; width: 100%;
justify-content: center; justify-content: center;
} }
}
/* Ensure the results grid has inner gutters and doesn't touch the edges */
.upload-results-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 1rem;
margin-top: 1.5rem;
padding: 0 0.5rem;
box-sizing: border-box;
} }

92
templates/partials/upload_result_card.html

@ -2,8 +2,8 @@
<div id="upload-card-{{.JobID}}" class="upload-result-card" data-job-id="{{.JobID}}"> <div id="upload-card-{{.JobID}}" class="upload-result-card" data-job-id="{{.JobID}}">
<div class="upload-header"> <div class="upload-header">
<h4>Job #{{.JobID}}</h4> <h4>Job #{{.JobID}}</h4>
<div class="upload-status {{if .Success}}success{{else}}error{{end}}"> <div class="upload-status {{if .DisplaySuccess}}success{{else}}error{{end}}">
{{if .Success}}✓ Success{{else}}✗ Failed{{end}} {{if .DisplaySuccess}}✓ Success{{else}}✗ Failed{{end}}
</div> </div>
</div> </div>
@ -11,7 +11,7 @@
<div class="upload-info"> <div class="upload-info">
<p><strong>Files Found:</strong> {{.FilesFound}}</p> <p><strong>Files Found:</strong> {{.FilesFound}}</p>
<p><strong>Files Uploaded:</strong> {{.FilesUploaded}}</p> <p><strong>Files Uploaded:</strong> {{.FilesUploaded}}</p>
{{if .Success}} {{if .DisplaySuccess}}
<p class="success-text">Successfully processed</p> <p class="success-text">Successfully processed</p>
{{else}} {{else}}
<p class="error-text">{{.ErrorMsg}}</p> <p class="error-text">{{.ErrorMsg}}</p>
@ -19,7 +19,7 @@
</div> </div>
<div class="upload-actions"> <div class="upload-actions">
{{if .Success}} {{if .DisplaySuccess}}
<div class="success-indicator"> <div class="success-indicator">
<span class="icon"></span> <span class="icon"></span>
<span>Upload Complete</span> <span>Upload Complete</span>
@ -43,7 +43,8 @@
<span class="success-icon"></span> <span class="success-icon"></span>
{{else}} {{else}}
<span class="error-icon"></span> <span class="error-icon"></span>
<span class="error-message">{{.Error}}</span> <span class="error-message clamp-text" data-full-text="{{.Error}}"
onclick="this.classList.toggle('expanded')">{{.Error}}</span>
{{end}} {{end}}
</div> </div>
{{end}} {{end}}
@ -52,14 +53,14 @@
<div class="file-pagination-buttons"> <div class="file-pagination-buttons">
{{if gt .FilePage 1}} {{if gt .FilePage 1}}
<button <button
hx-get="/documents/upload/job/file?job_id={{.JobID}}&session_id={{.SessionID}}&file_page={{subtract .FilePage 1}}" hx-get="/documents/upload/job/file?job_id={{.JobID}}&session_id={{.SessionID}}&file_page={{subtract .FilePage 1}}&filter={{.Filter}}"
hx-target="#upload-card-{{.JobID}}" hx-swap="outerHTML" hx-indicator="false" class="pagination-btn"> hx-target="#upload-card-{{.JobID}}" hx-swap="outerHTML" hx-indicator="false" class="pagination-btn">
← Previous File ← Previous File
</button> </button>
{{end}} {{end}}
{{if lt .FilePage .TotalFiles}} {{if lt .FilePage .TotalFiles}}
<button <button
hx-get="/documents/upload/job/file?job_id={{.JobID}}&session_id={{.SessionID}}&file_page={{add .FilePage 1}}" hx-get="/documents/upload/job/file?job_id={{.JobID}}&session_id={{.SessionID}}&file_page={{add .FilePage 1}}&filter={{.Filter}}"
hx-target="#upload-card-{{.JobID}}" hx-swap="outerHTML" hx-indicator="false" class="pagination-btn"> hx-target="#upload-card-{{.JobID}}" hx-swap="outerHTML" hx-indicator="false" class="pagination-btn">
Next File → Next File →
</button> </button>
@ -69,81 +70,4 @@
</div> </div>
{{end}} {{end}}
</div> </div>
{{end}}
{{define "upload_results_pagination"}}
<div class="upload-results-container">
<div class="upload-results-header">
<h3>Upload Results</h3>
<div class="upload-stats">
<div class="stat-item">
<span class="stat-value">{{.TotalJobs}}</span>
<span class="stat-label">Total Jobs</span>
</div>
<div class="stat-item success-stat">
<span class="stat-value">{{.TotalSuccess}}</span>
<span class="stat-label">Successful</span>
</div>
<div class="stat-item error-stat">
<span class="stat-value">{{.TotalFailure}}</span>
<span class="stat-label">Failed</span>
</div>
<div class="stat-item">
<span class="stat-value">{{printf "%.1f" (div .TotalBytesUploaded 1048576.0)}}</span>
<span class="stat-label">MB Uploaded</span>
</div>
<div class="stat-item">
<span class="stat-value">{{.TotalTime}}</span>
<span class="stat-label">Total Time</span>
</div>
</div>
</div>
{{if gt .TotalSuccess 0}}
<p>Successfully uploaded {{.TotalSuccess}} document(s) to ServiceTrade in {{.TotalTime}}!</p>
{{end}}
{{if gt .TotalFailure 0}}
<p class="text-warning">Failed to upload {{.TotalFailure}} document(s). See details below.</p>
{{end}}
<div class="upload-results-grid">
{{range .Results}}
{{template "upload_result_card" .}}
{{end}}
</div>
{{if gt .TotalPages 1}}
<div class="pagination-controls">
<div class="pagination-info">
Showing {{.StartIndex}}-{{.EndIndex}} of {{.TotalResults}} results
</div>
<div class="pagination-buttons">
{{if gt .CurrentPage 1}}
<button
hx-get="/documents/upload/results?page={{subtract .CurrentPage 1}}&limit={{.Limit}}&session_id={{.SessionID}}"
hx-target="#upload-results" hx-indicator="false" class="pagination-btn">
← Previous
</button>
{{end}}
{{range $i := sequence .StartPage .EndPage}}
<button hx-get="/documents/upload/results?page={{$i}}&limit={{$.Limit}}&session_id={{$.SessionID}}"
hx-target="#upload-results" hx-indicator="false"
class="pagination-btn {{if eq $i $.CurrentPage}}active{{end}}">
{{$i}}
</button>
{{end}}
{{if lt .CurrentPage .TotalPages}}
<button
hx-get="/documents/upload/results?page={{add .CurrentPage 1}}&limit={{.Limit}}&session_id={{.SessionID}}"
hx-target="#upload-results" hx-indicator="false" class="pagination-btn">
Next →
</button>
{{end}}
</div>
</div>
{{end}}
</div>
{{end}} {{end}}

7
templates/partials/upload_results_pagination.html

@ -27,14 +27,15 @@
<div class="pagination-buttons"> <div class="pagination-buttons">
{{if gt .CurrentPage 1}} {{if gt .CurrentPage 1}}
<button <button
hx-get="/documents/upload/results?page={{subtract .CurrentPage 1}}&limit={{.Limit}}&session_id={{.SessionID}}" hx-get="/documents/upload/results?page={{subtract .CurrentPage 1}}&limit={{.Limit}}&session_id={{.SessionID}}&filter={{.Filter}}"
hx-target="#upload-results" hx-indicator="false" class="pagination-btn"> hx-target="#upload-results" hx-indicator="false" class="pagination-btn">
← Previous ← Previous
</button> </button>
{{end}} {{end}}
{{range $i := sequence .StartPage .EndPage}} {{range $i := sequence .StartPage .EndPage}}
<button hx-get="/documents/upload/results?page={{$i}}&limit={{$.Limit}}&session_id={{$.SessionID}}" <button
hx-get="/documents/upload/results?page={{$i}}&limit={{$.Limit}}&session_id={{$.SessionID}}&filter={{$.Filter}}"
hx-target="#upload-results" hx-indicator="false" hx-target="#upload-results" hx-indicator="false"
class="pagination-btn {{if eq $i $.CurrentPage}}active{{end}}"> class="pagination-btn {{if eq $i $.CurrentPage}}active{{end}}">
{{$i}} {{$i}}
@ -43,7 +44,7 @@
{{if lt .CurrentPage .TotalPages}} {{if lt .CurrentPage .TotalPages}}
<button <button
hx-get="/documents/upload/results?page={{add .CurrentPage 1}}&limit={{.Limit}}&session_id={{.SessionID}}" hx-get="/documents/upload/results?page={{add .CurrentPage 1}}&limit={{.Limit}}&session_id={{.SessionID}}&filter={{.Filter}}"
hx-target="#upload-results" hx-indicator="false" class="pagination-btn"> hx-target="#upload-results" hx-indicator="false" class="pagination-btn">
Next → Next →
</button> </button>

14
templates/partials/upload_stats.html

@ -4,11 +4,21 @@
<span class="stat-value">{{.TotalJobs}}</span> <span class="stat-value">{{.TotalJobs}}</span>
<span class="stat-label">Total Jobs</span> <span class="stat-label">Total Jobs</span>
</div> </div>
<div class="stat-item success-stat"> <div class="stat-item all-stat"
hx-get="/documents/upload/results?page=1&limit={{.Limit}}&session_id={{.SessionID}}&filter=all"
hx-target="#upload-results" hx-indicator="false">
<span class="stat-value">{{add .TotalSuccess .TotalFailure}}</span>
<span class="stat-label">All Results</span>
</div>
<div class="stat-item success-stat"
hx-get="/documents/upload/results?page=1&limit={{.Limit}}&session_id={{.SessionID}}&filter=success"
hx-target="#upload-results" hx-indicator="false">
<span class="stat-value">{{.TotalSuccess}}</span> <span class="stat-value">{{.TotalSuccess}}</span>
<span class="stat-label">Successful</span> <span class="stat-label">Successful</span>
</div> </div>
<div class="stat-item error-stat"> <div class="stat-item error-stat"
hx-get="/documents/upload/results?page=1&limit={{.Limit}}&session_id={{.SessionID}}&filter=failed"
hx-target="#upload-results" hx-indicator="false">
<span class="stat-value">{{.TotalFailure}}</span> <span class="stat-value">{{.TotalFailure}}</span>
<span class="stat-label">Failed</span> <span class="stat-label">Failed</span>
</div> </div>

Loading…
Cancel
Save