Files
POPS/app/templates/job_detail.html
Martin Hohenberg 1259a7294a Remove internal filament management, integrate FilamentDB, add job detail page
Filament removal:
- Deleted app/models/filament.py, routers/filaments.py, templates/filaments.html
- Migration 0009: drops filaments table and filament_id FK from print_jobs
- Removed Filaments from sidebar and all internal references

FilamentDB integration (read-only):
- app/filamentdb.py: GET /reports/stock with 5-min in-memory cache
- available_materials() / available_colors() populate job form dropdowns
- Falls back to static constants if FilamentDB unreachable
- Dashboard filament count now from FilamentDB rolls_in_stock
- FILAMENTDB_URL added to .env.install; httpx added to requirements

Job detail page (/jobs/{id}):
- Two cards: Job info (customer, material, printer, bracket, timing, cost)
  and File & Geometry (bounding box + volume from stl_cache)
- Log timeline with Add Entry modal
- Filename in jobs list links to detail page

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

152 lines
6.3 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">
<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 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>
</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 text-secondary small text-uppercase fw-normal" style="border-color:#21262d">
File &amp; Geometry
</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 — will be computed on next upload.</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>
{# ── 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 %}