Files
POPS/app/templates/jobs.html
Martin Hohenberg ff2b618414 Duplicate job
2026-07-06 17:17:22 +02:00

251 lines
11 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 active_bracket %}
<div class="d-flex align-items-center gap-2 mb-3">
<span class="badge bg-secondary fs-6 fw-normal px-3 py-2">
<i class="bi bi-collection me-1"></i>{{ active_bracket.name }}
</span>
{% if bracket_gross_total is not none %}
<span class="badge bg-dark border border-secondary fs-6 fw-normal px-3 py-2 text-white">
<i class="bi bi-currency-euro me-1 text-secondary"></i>{{ "%.2f"|format(bracket_gross_total) }} <span class="text-secondary fw-normal" style="font-size:.75em">gross total</span>
</span>
{% endif %}
<a href="/jobs{% if q %}?q={{ q }}{% endif %}" class="text-secondary text-decoration-none small">× clear filter</a>
</div>
{% elif 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>
<th class="fw-normal"></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-warning text-dark">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-secondary">cancelled</span>
{% else %}<span class="badge border border-warning text-warning" style="background:#000">queued</span>
{% endif %}
</td>
<td class="text-secondary text-end">{{ j.created_at.strftime('%d.%m.%Y %H:%M') }}</td>
<td class="text-end" style="width:32px">
<form method="POST" action="/jobs/{{ j.id }}/duplicate" class="d-inline">
<button type="submit" class="btn btn-sm btn-link text-secondary p-0" title="Duplicate job">
<i class="bi bi-files"></i>
</button>
</form>
</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>
{% if hidden_count %}
{% set sep = '&' if (q or bracket_id) else '?' %}
{% set show_all_url = '/jobs?show_all=1' ~ ('&bracket_id=' ~ bracket_id if bracket_id else '') ~ ('&q=' ~ q if q else '') %}
<p class="text-secondary small mt-3 mb-0">
<i class="bi bi-archive me-1"></i>
{{ hidden_count }} finished job{{ 's' if hidden_count != 1 else '' }} older than 24 h hidden.
<a href="{{ show_all_url }}" class="text-secondary">Show all</a>
</p>
{% endif %}
{# ── 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
<span class="text-secondary fw-normal">(≥2 creates a bracket)</span>
</label>
<input type="number" name="amount"
class="form-control bg-dark border-secondary text-white"
value="1" min="1" max="500">
</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 %}