Compare commits
2 Commits
f713fbe2fb
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff2b618414 | ||
|
|
38e6d38a2f |
@@ -4,6 +4,12 @@ from app.vikunja import create_task, delete_task, get_tasks, update_task
|
|||||||
router = APIRouter(prefix="/todos", tags=["todos"])
|
router = APIRouter(prefix="/todos", tags=["todos"])
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/count")
|
||||||
|
def count_todos():
|
||||||
|
tasks = get_tasks()
|
||||||
|
return {"open": sum(1 for t in tasks if not t.get("done"))}
|
||||||
|
|
||||||
|
|
||||||
@router.get("/")
|
@router.get("/")
|
||||||
def list_todos():
|
def list_todos():
|
||||||
return get_tasks()
|
return get_tasks()
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import uuid as _uuid
|
import uuid as _uuid
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -90,10 +91,17 @@ def printer_detail(printer_id: int, request: Request, db: Session = Depends(get_
|
|||||||
printer = db.query(Printer).filter(Printer.id == printer_id).first()
|
printer = db.query(Printer).filter(Printer.id == printer_id).first()
|
||||||
if not printer:
|
if not printer:
|
||||||
raise HTTPException(status_code=404, detail="Printer not found")
|
raise HTTPException(status_code=404, detail="Printer not found")
|
||||||
|
current_job = (
|
||||||
|
db.query(PrintJob)
|
||||||
|
.filter(PrintJob.printer_id == printer_id, PrintJob.status == JobStatus.printing)
|
||||||
|
.order_by(PrintJob.started_at.desc())
|
||||||
|
.first()
|
||||||
|
)
|
||||||
return templates.TemplateResponse("printer_detail.html", {
|
return templates.TemplateResponse("printer_detail.html", {
|
||||||
"request": request,
|
"request": request,
|
||||||
"active": "printers",
|
"active": "printers",
|
||||||
"printer": printer,
|
"printer": printer,
|
||||||
|
"current_job": current_job,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@@ -335,6 +343,45 @@ def edit_job(
|
|||||||
return RedirectResponse(url=f"/jobs/{job_id}", status_code=HTTP_303_SEE_OTHER)
|
return RedirectResponse(url=f"/jobs/{job_id}", status_code=HTTP_303_SEE_OTHER)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/jobs/{job_id}/duplicate")
|
||||||
|
def duplicate_job(job_id: int, db: Session = Depends(get_db)):
|
||||||
|
from fastapi import HTTPException
|
||||||
|
job = db.query(PrintJob).filter(PrintJob.id == job_id).first()
|
||||||
|
if not job:
|
||||||
|
raise HTTPException(404, "Job not found")
|
||||||
|
|
||||||
|
copy = PrintJob(
|
||||||
|
job_uuid=str(_uuid.uuid4()),
|
||||||
|
name=job.name,
|
||||||
|
file_name=job.file_name,
|
||||||
|
printer_id=None,
|
||||||
|
bracket_id=job.bracket_id,
|
||||||
|
customer=job.customer,
|
||||||
|
job_material=job.job_material,
|
||||||
|
job_color=job.job_color,
|
||||||
|
priority=job.priority,
|
||||||
|
status=JobStatus.queued,
|
||||||
|
notes=job.notes,
|
||||||
|
)
|
||||||
|
db.add(copy)
|
||||||
|
|
||||||
|
# If the original was in a bracket, extend the "×N" count in its name to match.
|
||||||
|
if job.bracket and job.bracket.name:
|
||||||
|
job.bracket.name = re.sub(
|
||||||
|
r"×(\d+)\s*$",
|
||||||
|
lambda m: f"×{int(m.group(1)) + 1}",
|
||||||
|
job.bracket.name,
|
||||||
|
)
|
||||||
|
|
||||||
|
db.flush()
|
||||||
|
db.add(JobLog(job_id=copy.id, message=f"Job created (duplicate of #{job.id})"))
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
if copy.bracket_id:
|
||||||
|
return RedirectResponse(url=f"/jobs?bracket_id={copy.bracket_id}", status_code=HTTP_303_SEE_OTHER)
|
||||||
|
return RedirectResponse(url="/jobs", status_code=HTTP_303_SEE_OTHER)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/files/{filename}")
|
@router.get("/files/{filename}")
|
||||||
def download_file(filename: str):
|
def download_file(filename: str):
|
||||||
from fastapi import HTTPException
|
from fastapi import HTTPException
|
||||||
|
|||||||
@@ -60,7 +60,7 @@
|
|||||||
<li><a href="/printers" class="nav-link {% if active == 'printers' %}active{% endif %}"><i class="bi bi-printer me-2"></i>Printers</a></li>
|
<li><a href="/printers" class="nav-link {% if active == 'printers' %}active{% endif %}"><i class="bi bi-printer me-2"></i>Printers</a></li>
|
||||||
<li><a href="/jobs" class="nav-link {% if active == 'jobs' %}active{% endif %}"><i class="bi bi-list-task me-2"></i>Jobs</a></li>
|
<li><a href="/jobs" class="nav-link {% if active == 'jobs' %}active{% endif %}"><i class="bi bi-list-task me-2"></i>Jobs</a></li>
|
||||||
<li><a href="/brackets" class="nav-link {% if active == 'brackets' %}active{% endif %}"><i class="bi bi-collection me-2"></i>Brackets</a></li>
|
<li><a href="/brackets" class="nav-link {% if active == 'brackets' %}active{% endif %}"><i class="bi bi-collection me-2"></i>Brackets</a></li>
|
||||||
<li><a href="/todos" class="nav-link {% if active == 'todos' %}active{% endif %}"><i class="bi bi-check2-square me-2"></i>To-Do</a></li>
|
<li><a href="/todos" class="nav-link {% if active == 'todos' %}active{% endif %} d-flex align-items-center justify-content-between"><span><i class="bi bi-check2-square me-2"></i>To-Do</span><span id="todo-count-badge" class="badge rounded-pill bg-primary ms-1" style="display:none!important"></span></a></li>
|
||||||
<li><a href="/pricing" class="nav-link {% if active == 'pricing' %}active{% endif %}"><i class="bi bi-currency-euro me-2"></i>Pricing</a></li>
|
<li><a href="/pricing" class="nav-link {% if active == 'pricing' %}active{% endif %}"><i class="bi bi-currency-euro me-2"></i>Pricing</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
@@ -71,5 +71,14 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script>
|
||||||
|
fetch('/api/todos/count').then(r => r.json()).then(d => {
|
||||||
|
if (d.open > 0) {
|
||||||
|
const b = document.getElementById('todo-count-badge');
|
||||||
|
b.textContent = d.open;
|
||||||
|
b.style.removeProperty('display');
|
||||||
|
}
|
||||||
|
}).catch(() => {});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -50,6 +50,7 @@
|
|||||||
<th class="fw-normal">Material</th>
|
<th class="fw-normal">Material</th>
|
||||||
<th class="fw-normal">Status</th>
|
<th class="fw-normal">Status</th>
|
||||||
<th class="fw-normal text-end">Created</th>
|
<th class="fw-normal text-end">Created</th>
|
||||||
|
<th class="fw-normal"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -87,6 +88,13 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-secondary text-end">{{ j.created_at.strftime('%d.%m.%Y %H:%M') }}</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>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -16,6 +16,23 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{# ── Currently printing ── #}
|
||||||
|
{% if current_job %}
|
||||||
|
<a href="/jobs/{{ current_job.id }}" class="text-decoration-none">
|
||||||
|
<div class="card mb-4 border-primary">
|
||||||
|
<div class="card-body d-flex align-items-center gap-3">
|
||||||
|
<i class="bi bi-printer text-primary fs-4"></i>
|
||||||
|
<div>
|
||||||
|
<div class="text-secondary small text-uppercase">Currently printing</div>
|
||||||
|
<div class="text-white fw-semibold">{{ current_job.file_name or current_job.name or '—' }}</div>
|
||||||
|
{% if current_job.customer %}<div class="text-secondary small">{{ current_job.customer }}</div>{% endif %}
|
||||||
|
</div>
|
||||||
|
<i class="bi bi-chevron-right text-secondary ms-auto"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div class="row g-3 mb-4">
|
<div class="row g-3 mb-4">
|
||||||
|
|
||||||
{# ── Info card ── #}
|
{# ── Info card ── #}
|
||||||
|
|||||||
Reference in New Issue
Block a user