Files
POPS/app/templates/job_detail.html
Martin Hohenberg 6e43557ae0 Add job edit modal on detail page
POST /jobs/{id}/edit updates: customer, material, color, priority, bracket,
printer, status, notes. Bracket field has '— none —' to detach from bracket.
Save auto-logs 'Job updated'. Edit button in page header.

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

256 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 %}Job {{ job.job_uuid[:8] if job.job_uuid else job.id }} — POPS{% endblock %}
{% block content %}
<div class="d-flex align-items-center gap-3 mb-4">
<a href="{{ '/brackets/' ~ job.bracket.id if job.bracket else '/jobs' }}" class="text-secondary text-decoration-none">
<i class="bi bi-arrow-left"></i>
</a>
<div class="flex-grow-1 me-3">
<div class="d-flex align-items-center gap-2">
<h5 class="text-white mb-0">{{ job.file_name or job.name }}</h5>
{% set s = job.status.value %}
{% if job.priority %}<span class="badge bg-warning text-dark"><i class="bi bi-exclamation-circle me-1"></i>Priority</span>{% endif %}
{% 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 %}
</div>
<div class="text-secondary small font-monospace mt-1">{{ job.job_uuid or '—' }}</div>
</div>
<button class="btn btn-sm btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#editJobModal">
<i class="bi bi-pencil me-1"></i>Edit
</button>
</div>
<div class="row g-3 mb-4">
{# ── Job info ── #}
<div class="col-md-6">
<div class="card h-100">
<div class="card-header text-secondary small text-uppercase fw-normal" style="border-color:#21262d">Job</div>
<div class="card-body">
<table class="table table-dark table-sm mb-0" style="font-size:.875rem">
<tbody>
<tr><td class="text-secondary border-0" style="width:40%">Customer</td>
<td class="border-0">{{ job.customer or '—' }}</td></tr>
<tr><td class="text-secondary">Material</td>
<td>{{ job.job_material or '—' }}{% if job.job_color %} · {{ job.job_color }}{% endif %}</td></tr>
<tr><td class="text-secondary">Printer</td>
<td>{{ job.printer.name if job.printer else '—' }}</td></tr>
<tr><td class="text-secondary">Bracket</td>
<td>{% if job.bracket %}<a href="/brackets/{{ job.bracket.id }}" class="text-white">{{ job.bracket.name }}</a>{% else %}—{% endif %}</td></tr>
<tr><td class="text-secondary">Created</td>
<td>{{ job.created_at.strftime('%d.%m.%Y %H:%M') }}</td></tr>
{% if job.started_at %}
<tr><td class="text-secondary">Started</td>
<td>{{ job.started_at.strftime('%d.%m.%Y %H:%M') }}</td></tr>
{% endif %}
{% if job.finished_at %}
<tr><td class="text-secondary">Finished</td>
<td>{{ job.finished_at.strftime('%d.%m.%Y %H:%M') }}</td></tr>
{% endif %}
{% if job.duration_minutes %}
<tr><td class="text-secondary">Duration</td>
<td>{{ job.duration_minutes }} min</td></tr>
{% endif %}
{% if job.cost_eur %}
<tr><td class="text-secondary">Cost</td>
<td>€ {{ "%.2f"|format(job.cost_eur) }}</td></tr>
{% endif %}
{% if job.notes %}
<tr><td class="text-secondary">Notes</td>
<td>{{ job.notes }}</td></tr>
{% endif %}
</tbody>
</table>
</div>
</div>
</div>
{# ── STL geometry ── #}
<div class="col-md-6">
<div class="card h-100">
<div class="card-header border-secondary d-flex align-items-center justify-content-between" style="border-color:#21262d">
<span class="text-secondary small text-uppercase fw-normal">File &amp; Geometry</span>
{% if job.file_name and not job.file_name.endswith('.gcode') %}
<form method="POST" action="/jobs/{{ job.id }}/analyse">
<button type="submit" class="btn btn-outline-secondary btn-sm py-0 px-2" style="font-size:.75rem">
<i class="bi bi-cpu me-1"></i>Compute now
</button>
</form>
{% endif %}
</div>
<div class="card-body">
{% if job.file_name %}
<div class="text-white mb-3 font-monospace" style="font-size:.85rem">{{ job.file_name }}</div>
{% endif %}
{% if stl %}
<table class="table table-dark table-sm mb-0" style="font-size:.875rem">
<tbody>
<tr><td class="text-secondary border-0">Bounding box</td>
<td class="border-0">
{{ stl.cube_x|round(1) }} × {{ stl.cube_y|round(1) }} × {{ stl.cube_z|round(1) }} mm
</td></tr>
<tr><td class="text-secondary">Volume</td>
<td>{{ stl.volume_ccm }} cm³</td></tr>
</tbody>
</table>
{% elif job.file_name and job.file_name.endswith('.gcode') %}
<p class="text-secondary small mb-0">Geometry not available for gcode files.</p>
{% elif job.file_name %}
<p class="text-secondary small mb-0">Not yet analysed.</p>
{% else %}
<p class="text-secondary small mb-0">No file attached.</p>
{% endif %}
</div>
</div>
</div>
</div>
{# ── Log ── #}
<div class="d-flex align-items-center justify-content-between mb-3">
<h6 class="text-white mb-0">Log</h6>
<button class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#addLogModal">
<i class="bi bi-plus-lg me-1"></i>Add entry
</button>
</div>
<div class="card">
{% if job.logs %}
<table class="table table-dark table-hover mb-0 align-middle">
<tbody>
{% for entry in job.logs %}
<tr>
<td class="text-secondary small" style="width:140px;white-space:nowrap">
{{ entry.created_at.strftime('%d.%m.%Y %H:%M') }}
</td>
<td>{{ entry.message }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<div class="card-body text-secondary">No log entries.</div>
{% endif %}
</div>
{# ── Edit Job Modal ── #}
<div class="modal fade" id="editJobModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content bg-dark border-secondary">
<form method="POST" action="/jobs/{{ job.id }}/edit">
<div class="modal-header border-secondary">
<h6 class="modal-title text-white">Edit 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">Customer</label>
<input type="text" name="customer" value="{{ job.customer or '' }}"
class="form-control bg-dark border-secondary text-white">
</div>
<div class="col-md-6">
<label class="form-label text-secondary small">Status</label>
<select name="status" class="form-select bg-dark border-secondary text-white">
{% for s in all_statuses %}
<option value="{{ s }}" {% if job.status.value == s %}selected{% endif %}>{{ s }}</option>
{% endfor %}
</select>
</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 value="{{ m }}" {% if job.job_material == m %}selected{% endif %}>{{ 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 value="{{ c }}" {% if job.job_color == c %}selected{% endif %}>{{ c }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label text-secondary small">Printer</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 }}" {% if job.printer_id == p.id %}selected{% endif %}>{{ p.name }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-6">
<label class="form-label text-secondary small">Bracket</label>
<select name="bracket_id" class="form-select bg-dark border-secondary text-white">
<option value="">— none —</option>
{% for b in brackets %}
<option value="{{ b.id }}" {% if job.bracket_id == b.id %}selected{% endif %}>{{ b.name }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" name="priority" id="editPriorityCheck"
value="1" {% if job.priority %}checked{% endif %}>
<label class="form-check-label text-secondary small" for="editPriorityCheck">
<i class="bi bi-exclamation-circle text-warning me-1"></i>Priority
</label>
</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">{{ job.notes or '' }}</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">Save</button>
</div>
</form>
</div>
</div>
</div>
{# ── Add log entry modal ── #}
<div class="modal fade" id="addLogModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content bg-dark border-secondary">
<form method="POST" action="/jobs/{{ job.id }}/log">
<div class="modal-header border-secondary">
<h6 class="modal-title text-white">Add Log Entry</h6>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<textarea name="message" class="form-control bg-dark border-secondary text-white"
rows="3" placeholder="What happened?" required autofocus></textarea>
</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">Add</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}