Replace bracket detail page with /jobs?bracket_id= filter
- Bracket names in the list now link directly to /jobs?bracket_id=X
- /brackets/{id} redirects to /jobs?bracket_id=X (keeps old links working)
- Bracket link on job detail page updated accordingly
- bracket_detail.html and add_jobs_to_bracket route removed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -308,58 +308,9 @@ def create_bracket(
|
|||||||
return RedirectResponse(url="/brackets", status_code=HTTP_303_SEE_OTHER)
|
return RedirectResponse(url="/brackets", status_code=HTTP_303_SEE_OTHER)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/brackets/{bracket_id}", response_class=HTMLResponse)
|
@router.get("/brackets/{bracket_id}")
|
||||||
def bracket_detail(bracket_id: int, request: Request, db: Session = Depends(get_db)):
|
def bracket_detail_redirect(bracket_id: int):
|
||||||
from fastapi import HTTPException
|
return RedirectResponse(url=f"/jobs?bracket_id={bracket_id}", status_code=HTTP_303_SEE_OTHER)
|
||||||
bracket = db.query(JobBracket).filter(JobBracket.id == bracket_id).first()
|
|
||||||
if not bracket:
|
|
||||||
raise HTTPException(status_code=404, detail="Bracket not found")
|
|
||||||
return templates.TemplateResponse("bracket_detail.html", {
|
|
||||||
"request": request,
|
|
||||||
"active": "brackets",
|
|
||||||
"bracket": bracket,
|
|
||||||
"stl_files": (stl_files := list_stl_files()),
|
|
||||||
"stl_cache": get_cache_map(stl_files, db),
|
|
||||||
"materials": available_materials() or MATERIALS,
|
|
||||||
"colors": available_colors() or COLORS,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/brackets/{bracket_id}/jobs")
|
|
||||||
async def add_jobs_to_bracket(
|
|
||||||
bracket_id: int,
|
|
||||||
quantity: int = Form(1),
|
|
||||||
job_material: str = Form(""),
|
|
||||||
job_color: str = Form(""),
|
|
||||||
notes: str = Form(""),
|
|
||||||
stl_file: UploadFile = File(None),
|
|
||||||
stl_select: str = Form(""),
|
|
||||||
db: Session = Depends(get_db),
|
|
||||||
):
|
|
||||||
filename = None
|
|
||||||
if stl_file and stl_file.filename:
|
|
||||||
filename = stl_file.filename
|
|
||||||
(get_stl_dir() / filename).write_bytes(await stl_file.read())
|
|
||||||
analyse(filename, db)
|
|
||||||
elif stl_select:
|
|
||||||
filename = stl_select
|
|
||||||
|
|
||||||
for _ in range(max(1, min(quantity, 500))):
|
|
||||||
job = PrintJob(
|
|
||||||
job_uuid=str(_uuid.uuid4()),
|
|
||||||
name=Path(filename).stem if filename else "Unnamed",
|
|
||||||
file_name=filename,
|
|
||||||
bracket_id=bracket_id,
|
|
||||||
job_material=job_material or None,
|
|
||||||
job_color=job_color or None,
|
|
||||||
notes=notes.strip() or None,
|
|
||||||
)
|
|
||||||
db.add(job)
|
|
||||||
db.flush()
|
|
||||||
db.add(JobLog(job_id=job.id, message="Job created"))
|
|
||||||
|
|
||||||
db.commit()
|
|
||||||
return RedirectResponse(url=f"/brackets/{bracket_id}", status_code=HTTP_303_SEE_OTHER)
|
|
||||||
|
|
||||||
|
|
||||||
# ── To-Do ──────────────────────────────────────────────────────────────────────
|
# ── To-Do ──────────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -1,170 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
{% block title %}{{ bracket.name }} — POPS{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% set total = bracket.jobs | length %}
|
|
||||||
{% set done = bracket.jobs | selectattr('status.value', 'equalto', 'done') | list | length %}
|
|
||||||
{% set printing = bracket.jobs | selectattr('status.value', 'equalto', 'printing') | list | length %}
|
|
||||||
{% set pct = (done / total * 100) | int if total else 0 %}
|
|
||||||
|
|
||||||
<div class="d-flex align-items-center gap-3 mb-4">
|
|
||||||
<a href="/brackets" class="text-secondary text-decoration-none"><i class="bi bi-arrow-left"></i></a>
|
|
||||||
<div class="flex-grow-1">
|
|
||||||
<h5 class="text-white mb-0">{{ bracket.name }}</h5>
|
|
||||||
{% if bracket.customer %}<small class="text-secondary">{{ bracket.customer }}</small>{% endif %}
|
|
||||||
</div>
|
|
||||||
<button class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#addJobsModal">
|
|
||||||
<i class="bi bi-plus-lg me-1"></i>Add Job(s)
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{# ── Progress ── #}
|
|
||||||
{% if total %}
|
|
||||||
<div class="card mb-4 p-3">
|
|
||||||
<div class="d-flex justify-content-between mb-2">
|
|
||||||
<span class="text-secondary small">{{ done }} done · {{ printing }} printing · {{ total - done - printing }} queued</span>
|
|
||||||
<span class="text-secondary small">{{ pct }}%</span>
|
|
||||||
</div>
|
|
||||||
<div class="progress" style="height:8px">
|
|
||||||
<div class="progress-bar bg-success" style="width:{{ pct }}%"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{# ── Jobs table ── #}
|
|
||||||
<div class="card">
|
|
||||||
{% if bracket.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">UUID</th>
|
|
||||||
<th class="fw-normal">File</th>
|
|
||||||
<th class="fw-normal">Material</th>
|
|
||||||
<th class="fw-normal">Printer</th>
|
|
||||||
<th class="fw-normal">Status</th>
|
|
||||||
<th class="fw-normal text-end">Created</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for j in bracket.jobs %}
|
|
||||||
<tr>
|
|
||||||
<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.file_name or j.name }}</td>
|
|
||||||
<td class="text-secondary">
|
|
||||||
{% if j.job_material %}{{ j.job_material }}{% if j.job_color %} · {{ j.job_color }}{% endif %}{% else %}—{% endif %}
|
|
||||||
</td>
|
|
||||||
<td class="text-secondary">{{ j.printer.name if j.printer else '—' }}</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 %H:%M') }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{% else %}
|
|
||||||
<div class="card-body text-secondary">No jobs in this bracket yet.</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{# ── Add Jobs Modal ── #}
|
|
||||||
<div class="modal fade" id="addJobsModal" tabindex="-1">
|
|
||||||
<div class="modal-dialog modal-lg">
|
|
||||||
<div class="modal-content bg-dark border-secondary">
|
|
||||||
<form method="POST" action="/brackets/{{ bracket.id }}/jobs" enctype="multipart/form-data">
|
|
||||||
<div class="modal-header border-secondary">
|
|
||||||
<h6 class="modal-title text-white">Add Job(s) to Bracket</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">
|
|
||||||
|
|
||||||
{# STL file #}
|
|
||||||
<div>
|
|
||||||
<label class="form-label text-secondary small">STL / File</label>
|
|
||||||
<ul class="nav nav-pills mb-2" 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="#bUploadPane" 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="#bLibraryPane" 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="bUploadPane">
|
|
||||||
<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="bLibraryPane">
|
|
||||||
{% 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.</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row g-3">
|
|
||||||
<div class="col-md-4">
|
|
||||||
<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-4">
|
|
||||||
<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 class="col-md-4">
|
|
||||||
<label class="form-label text-secondary small">
|
|
||||||
Quantity
|
|
||||||
<span class="text-secondary fw-normal">(copies to create)</span>
|
|
||||||
</label>
|
|
||||||
<input type="number" name="quantity"
|
|
||||||
class="form-control bg-dark border-secondary text-white"
|
|
||||||
value="1" min="1" max="500">
|
|
||||||
</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 (applied to all copies)"></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">Add Jobs</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
{% set done = b.jobs | selectattr('status.value', 'equalto', 'done') | list | length %}
|
{% set done = b.jobs | selectattr('status.value', 'equalto', 'done') | list | length %}
|
||||||
{% set pct = (done / total * 100) | int if total else 0 %}
|
{% set pct = (done / total * 100) | int if total else 0 %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="/brackets/{{ b.id }}" class="text-white text-decoration-none fw-medium">{{ b.name }}</a></td>
|
<td><a href="/jobs?bracket_id={{ b.id }}" class="text-white text-decoration-none fw-medium">{{ b.name }}</a></td>
|
||||||
<td class="text-secondary">{{ b.customer or '—' }}</td>
|
<td class="text-secondary">{{ b.customer or '—' }}</td>
|
||||||
<td style="min-width:200px">
|
<td style="min-width:200px">
|
||||||
{% if total %}
|
{% if total %}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
<tr><td class="text-secondary">Printer</td>
|
<tr><td class="text-secondary">Printer</td>
|
||||||
<td>{{ job.printer.name if job.printer else '—' }}</td></tr>
|
<td>{{ job.printer.name if job.printer else '—' }}</td></tr>
|
||||||
<tr><td class="text-secondary">Bracket</td>
|
<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>
|
<td>{% if job.bracket %}<a href="/jobs?bracket_id={{ job.bracket.id }}" class="text-white">{{ job.bracket.name }}</a>{% else %}—{% endif %}</td></tr>
|
||||||
<tr><td class="text-secondary">Created</td>
|
<tr><td class="text-secondary">Created</td>
|
||||||
<td>{{ job.created_at.strftime('%d.%m.%Y %H:%M') }}</td></tr>
|
<td>{{ job.created_at.strftime('%d.%m.%Y %H:%M') }}</td></tr>
|
||||||
{% if job.started_at %}
|
{% if job.started_at %}
|
||||||
|
|||||||
Reference in New Issue
Block a user