fix(ui): jobs page — semantic cards, remove onclick, flat progress bar
This commit is contained in:
1 parent
b1170f42a9
commit
20d523469d
2 files changed
+60
-13
No files matched your search
@@ -0,0 +1,40 @@
|
||||
## Task 6: Jobs Page Redesign — Report
|
||||
|
||||
### Changes Made
|
||||
|
||||
**`templates/jobs.html`** — Complete rewrite from table layout to responsive card grid.
|
||||
|
||||
- Page heading changed from "All Jobs" to "Processing Jobs"
|
||||
- Card grid layout with responsive columns: 1 col (mobile), 2 col (640px+), 3 col (1024px+)
|
||||
- Each job card contains:
|
||||
- Thumbnail image from `/preview/thumb/<job_id>` with SVG placeholder fallback on error
|
||||
- Status badge (top-right corner) using `.badge-*` classes: success (COMPLETED), info (RUNNING), warning (PENDING), error (FAILED), neutral (CANCELLED)
|
||||
- Truncated job ID (12 chars + ellipsis) with copy-to-clipboard button (green checkmark feedback)
|
||||
- Video filename extracted from `job.video_path`
|
||||
- Thin 4px progress bar with model count and percentage
|
||||
- Relative timestamp (e.g. "2 minutes ago") with auto-update every 60s
|
||||
- "View Details" link to `/status/<job_id>`
|
||||
- Empty state with film-strip SVG icon, "No jobs yet" heading, and "Upload a video" CTA button
|
||||
- Scoped `<style>` block with design system tokens (no new CSS in `style.css`)
|
||||
- Inline `<script>` for relative time formatting and clipboard copy functionality
|
||||
|
||||
### Verification
|
||||
|
||||
- Template loads without Jinja2 errors
|
||||
- Flask `/jobs` endpoint returns 200 OK
|
||||
- Empty state renders correctly (no jobs by default)
|
||||
- All required elements present in rendered HTML: page header, grid, card structure, thumbnails, badges, copy button, progress bars, relative times, empty state, CTA button
|
||||
|
||||
### Commit
|
||||
|
||||
`b1170f4` — `feat(ui): jobs page — card grid with badges and thumbnails`
|
||||
|
||||
---
|
||||
|
||||
### Review Fixes (3 findings)
|
||||
|
||||
1. **Non-semantic card container** — Changed `<div class="card job-card">` → `<article class="card job-card">` (and closing tag) for better screen reader navigation.
|
||||
|
||||
2. **`onclick` on copy button** — Removed inline `onclick="copyJobId(this, '{{ job.job_id }}')"` attribute; added `data-job-id="{{ job.job_id }}"` for identification; moved copy logic to `addEventListener('click', ...)` in the `<script>` block using `document.querySelectorAll('.copy-btn')`.
|
||||
|
||||
3. **Gradient on progress bar** — Replaced `linear-gradient(90deg, var(--color-primary-500), var(--color-primary-400))` with solid `var(--color-primary-500)` to match flat design system.
|
||||
+20
-13
@@ -190,7 +190,7 @@
|
||||
|
||||
.job-card-progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, var(--color-primary-500), var(--color-primary-400));
|
||||
background: var(--color-primary-500);
|
||||
border-radius: 2px;
|
||||
transition: width 0.4s ease;
|
||||
}
|
||||
@@ -227,7 +227,7 @@
|
||||
{% if jobs %}
|
||||
<div class="jobs-grid">
|
||||
{% for job in jobs %}
|
||||
<div class="card job-card">
|
||||
<article class="card job-card">
|
||||
<div class="job-card-thumb">
|
||||
<img src="/preview/thumb/{{ job.job_id }}" alt="Thumbnail for job {{ job.job_id }}" loading="lazy"
|
||||
onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
|
||||
@@ -248,7 +248,7 @@
|
||||
<div class="job-card-body">
|
||||
<div class="job-card-header">
|
||||
<span class="job-card-id" title="{{ job.job_id }}">{{ job.job_id[:12] }}{% if job.job_id|length > 12 %}…{% endif %}</span>
|
||||
<button class="copy-btn" onclick="copyJobId(this, '{{ job.job_id }}')" title="Copy Job ID" aria-label="Copy Job ID">
|
||||
<button class="copy-btn" data-job-id="{{ job.job_id }}" title="Copy Job ID" aria-label="Copy Job ID">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
@@ -267,7 +267,7 @@
|
||||
<span class="job-card-time" data-timestamp="{{ job.created_at }}"></span>
|
||||
<a href="/status/{{ job.job_id }}" class="job-card-link">View Details</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
@@ -321,15 +321,22 @@
|
||||
setInterval(updateTimes, 60000);
|
||||
})();
|
||||
|
||||
function copyJobId(btn, id) {
|
||||
navigator.clipboard.writeText(id).then(function() {
|
||||
btn.classList.add('copied');
|
||||
btn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>';
|
||||
setTimeout(function() {
|
||||
btn.classList.remove('copied');
|
||||
btn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
|
||||
}, 1500);
|
||||
var copySvg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
|
||||
var checkSvg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>';
|
||||
|
||||
document.querySelectorAll('.copy-btn').forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
var id = this.getAttribute('data-job-id');
|
||||
var el = this;
|
||||
navigator.clipboard.writeText(id).then(function() {
|
||||
el.classList.add('copied');
|
||||
el.innerHTML = checkSvg;
|
||||
setTimeout(function() {
|
||||
el.classList.remove('copied');
|
||||
el.innerHTML = copySvg;
|
||||
}, 1500);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in new issue
Block a user