Files
POPS/app/templates/jobs.html
Martin Hohenberg 392acef28e Show spool availability indicator per job on jobs list
- filamentdb.spool_counts(): builds (material, color_name) → roll count
  from cached stock report; counts rolls_in_stock + rolls_in_use (non-graveyard)
- Jobs list: coloured dot before material — green (2+), yellow (1), red (0)
- FILAMENTDB_URL added to .env

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 22:22:32 +02:00

219 lines
9.7 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block title %}Jobs — POPS{% endblock %}
{% block content %}
<div class="d-flex align-items-center justify-content-between mb-4">
<h5 class="text-white mb-0">Print Jobs</h5>
<button class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#addJobModal">
<i class="bi bi-plus-lg me-1"></i>Add Job
</button>
</div>
{# ── Search bar ── #}
<form method="GET" action="/jobs" class="mb-3">
<div class="input-group input-group-sm" style="max-width:360px">
<span class="input-group-text bg-dark border-secondary text-secondary"><i class="bi bi-search"></i></span>
<input type="text" name="q" value="{{ q }}"
class="form-control bg-dark border-secondary text-white"
placeholder="Search by customer or filename…">
{% if q %}
<a href="/jobs" class="btn btn-outline-secondary">×</a>
{% endif %}
</div>
</form>
{% if q %}
<p class="text-secondary small mb-3">Results for <strong class="text-white">{{ q }}</strong></p>
{% endif %}
<div class="card">
{% if jobs %}
<table class="table table-dark table-hover mb-0 align-middle" style="font-size:.875rem">
<thead>
<tr class="text-secondary small">
<th class="fw-normal"></th>
<th class="fw-normal">UUID</th>
<th class="fw-normal">Customer</th>
<th class="fw-normal">File</th>
<th class="fw-normal">Printer</th>
<th class="fw-normal">Material</th>
<th class="fw-normal">Status</th>
<th class="fw-normal text-end">Created</th>
</tr>
</thead>
<tbody>
{% for j in jobs %}
<tr>
<td style="width:24px">
{% if j.priority %}<i class="bi bi-exclamation-circle-fill text-warning" title="Priority"></i>{% endif %}
</td>
<td class="font-monospace text-secondary" style="font-size:.75rem">
{{ j.job_uuid[:8] if j.job_uuid else '—' }}
</td>
<td class="text-white">{{ j.customer or '—' }}</td>
<td><a href="/jobs/{{ j.id }}" class="text-secondary text-decoration-none">{{ j.file_name or j.name or '—' }}</a></td>
<td class="text-secondary">{{ j.printer.name if j.printer else '—' }}</td>
<td class="text-secondary">
{% if j.job_material %}
{% set count = spool_map.get((j.job_material, j.job_color or ''), 0) %}
{% if count >= 2 %}
<span class="text-success me-1" title="{{ count }} rolls available"></span>
{% elif count == 1 %}
<span class="text-warning me-1" title="1 roll available"></span>
{% else %}
<span class="text-danger me-1" title="No rolls in stock"></span>
{% endif %}
{{ j.job_material }}{% if j.job_color %} · {{ j.job_color }}{% endif %}
{% else %}—{% endif %}
</td>
<td>
{% set s = j.status.value %}
{% if s == 'printing' %}<span class="badge bg-primary">printing</span>
{% elif s == 'done' %}<span class="badge bg-success">done</span>
{% elif s == 'failed' %}<span class="badge bg-danger">failed</span>
{% elif s == 'cancelled' %}<span class="badge bg-warning text-dark">cancelled</span>
{% else %}<span class="badge bg-secondary">queued</span>
{% endif %}
</td>
<td class="text-secondary text-end">{{ j.created_at.strftime('%d.%m.%Y %H:%M') }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<div class="card-body text-secondary">
{% if q %}No jobs match "{{ q }}".{% else %}No jobs yet.{% endif %}
</div>
{% endif %}
</div>
{# ── Add Job Modal ── #}
<div class="modal fade" id="addJobModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content bg-dark border-secondary">
<form method="POST" action="/jobs" enctype="multipart/form-data">
<div class="modal-header border-secondary">
<h6 class="modal-title text-white">Add Print Job</h6>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body d-flex flex-column gap-3">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label text-secondary small">Printer <span class="text-secondary fw-normal">(assigned when starting)</span></label>
<select name="printer_id" class="form-select bg-dark border-secondary text-white">
<option value="">— unassigned —</option>
{% for p in printers %}
<option value="{{ p.id }}">{{ p.name }}{% if p.location %} ({{ p.location }}){% endif %}</option>
{% endfor %}
</select>
</div>
<div class="col-md-6">
<label class="form-label text-secondary small">Customer</label>
<input type="text" name="customer" class="form-control bg-dark border-secondary text-white"
placeholder="Customer name or reference">
</div>
</div>
{# ── STL file ── #}
<div>
<label class="form-label text-secondary small">STL / File</label>
<ul class="nav nav-pills mb-2" id="stlTabs" role="tablist">
<li class="nav-item">
<button class="nav-link active py-1 px-3" style="font-size:.8rem"
data-bs-toggle="pill" data-bs-target="#uploadPane" type="button">
<i class="bi bi-upload me-1"></i>Upload
</button>
</li>
<li class="nav-item">
<button class="nav-link py-1 px-3" style="font-size:.8rem"
data-bs-toggle="pill" data-bs-target="#libraryPane" type="button">
<i class="bi bi-folder2-open me-1"></i>Library
<span class="badge bg-secondary ms-1">{{ stl_files | length }}</span>
</button>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane fade show active" id="uploadPane">
<input type="file" name="stl_file"
class="form-control bg-dark border-secondary text-white"
accept=".stl,.3mf,.gcode">
</div>
<div class="tab-pane fade" id="libraryPane">
{% if stl_files %}
<select name="stl_select" class="form-select bg-dark border-secondary text-white">
<option value="">— select file —</option>
{% for f in stl_files %}
{% set c = stl_cache.get(f) %}
<option value="{{ f }}">{{ f }}{% if c %} — {{ c.cube_x|round(1) }}×{{ c.cube_y|round(1) }}×{{ c.cube_z|round(1) }} mm · {{ c.volume_ccm }} cm³{% endif %}</option>
{% endfor %}
</select>
{% else %}
<p class="text-secondary small mb-0">No files in library yet. Upload one first.</p>
{% endif %}
</div>
</div>
</div>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label text-secondary small">Material</label>
<select name="job_material" class="form-select bg-dark border-secondary text-white">
<option value="">— none —</option>
{% for m in materials %}<option>{{ m }}</option>{% endfor %}
</select>
</div>
<div class="col-md-6">
<label class="form-label text-secondary small">Color</label>
<select name="job_color" class="form-select bg-dark border-secondary text-white">
<option value="">— none —</option>
{% for c in colors %}<option>{{ c }}</option>{% endfor %}
</select>
</div>
</div>
<div class="form-check form-switch mb-1">
<input class="form-check-input" type="checkbox" name="priority" id="priorityCheck" value="1">
<label class="form-check-label text-secondary small" for="priorityCheck">
<i class="bi bi-exclamation-circle text-warning me-1"></i>Priority
</label>
</div>
<div class="row g-3">
<div class="col-md-4">
<label class="form-label text-secondary small">Amount</label>
<input type="number" name="amount"
class="form-control bg-dark border-secondary text-white"
value="1" min="1" max="500"
title="&gt;1 auto-creates a bracket">
</div>
<div class="col-md-8">
<label class="form-label text-secondary small">
Bracket
<span class="text-secondary fw-normal">(auto-created when amount &gt; 1)</span>
</label>
<select name="bracket_id" class="form-select bg-dark border-secondary text-white">
<option value="">— none / auto —</option>
{% for b in brackets %}
<option value="{{ b.id }}">{{ b.name }}{% if b.customer %} · {{ b.customer }}{% endif %}</option>
{% endfor %}
</select>
</div>
</div>
<div>
<label class="form-label text-secondary small">Notes</label>
<textarea name="notes" class="form-control bg-dark border-secondary text-white"
rows="2" placeholder="Optional notes"></textarea>
</div>
</div>
<div class="modal-footer border-secondary">
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary btn-sm">Create Job</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}