ixg_platform/templates/ops/specification_list.html
ixgadmin df2a90bc71 Add the product specification screen to the ops portal
Operations can now create and edit a specification and its parameters
together, over the existing model, mirroring the admin's inline.

The approval half is deliberately held: the form does not expose status,
is_current, approved_by or approved_at, so a portal-created specification
is a draft and cannot be approved here. The edit screen warns when the
record is not a draft, because its parameters are the limits QC results
are judged against. Nothing is removed from the admin.

The parameter formset refuses an empty specification (with no parameters
every QC result would pass), a numeric parameter with no limits, and a
visual parameter with no allowed values.
2026-09-25 16:35:45 +00:00

100 lines
3.9 KiB
HTML

{% extends 'ops/base_ops.html' %}
{% block title %}Specifications - IXG Operations{% endblock %}
{% block content %}
{% if messages %}
{% for m in messages %}<div class="alert alert-{{ m.tags }} py-2">{{ m }}</div>{% endfor %}
{% endif %}
<div class="page-header d-flex justify-content-between align-items-center">
<h2>Product Specifications</h2>
<div>
<span class="text-secondary me-2">{{ page_obj.paginator.count }} total</span>
<a href="{% url 'ops:specification_create' %}" class="btn btn-sm btn-primary"><i class="bi bi-plus-lg"></i> New specification</a>
</div>
</div>
<div class="alert alert-light border py-2 small">
A specification is the set of limits every QC result is judged against. The portal can
create and edit one, but <strong>approval is held</strong>: a specification created here
is a draft and cannot be approved from the portal yet.
</div>
<div class="filter-bar">
<form method="get" class="row g-2 align-items-end">
<div class="col-auto">
<input type="text" name="product" class="form-control form-control-sm" placeholder="Search product..." value="{{ current_product }}">
</div>
<div class="col-auto">
<select name="status" class="form-select form-select-sm">
<option value="">All Statuses</option>
{% for val, label in status_choices %}
<option value="{{ val }}" {% if current_status == val %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</div>
<div class="col-auto">
<button type="submit" class="btn btn-sm btn-primary">Filter</button>
<a href="{% url 'ops:specification_list' %}" class="btn btn-sm btn-outline-secondary">Clear</a>
</div>
</form>
</div>
<div class="stat-card p-0">
<table class="table table-hover mb-0">
<thead>
<tr>
<th>Product</th>
<th>Version</th>
<th>Status</th>
<th>Current</th>
<th>Effective from</th>
<th>Effective to</th>
<th>Parameters</th>
<th></th>
</tr>
</thead>
<tbody>
{% for s in specs %}
<tr>
<td><strong>{{ s.product }}</strong></td>
<td>{{ s.version }}</td>
<td>
{% if s.status == 'approved' %}
<span class="badge bg-success">{{ s.get_status_display }}</span>
{% elif s.status == 'superseded' %}
<span class="badge bg-warning text-dark">{{ s.get_status_display }}</span>
{% else %}
<span class="badge bg-secondary">{{ s.get_status_display }}</span>
{% endif %}
</td>
<td>{% if s.is_current %}<i class="bi bi-check-circle-fill text-success"></i>{% else %}-{% endif %}</td>
<td>{{ s.effective_from|date:'d/m/Y'|default:'-' }}</td>
<td>{{ s.effective_to|date:'d/m/Y'|default:'-' }}</td>
<td>{{ s.parameters.all|length }}</td>
<td><a href="{% url 'ops:specification_update' s.pk %}" class="btn btn-sm btn-outline-primary">Edit</a></td>
</tr>
{% empty %}
<tr><td colspan="8" class="text-muted text-center py-3">No specifications yet</td></tr>
{% endfor %}
</tbody>
</table>
</div>
{% if page_obj.paginator.num_pages > 1 %}
<nav class="mt-3">
<ul class="pagination pagination-sm justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item"><a class="page-link" href="?page=1">&laquo;</a></li>
<li class="page-item"><a class="page-link" href="?page={{ page_obj.previous_page_number }}">{{ page_obj.previous_page_number }}</a></li>
{% endif %}
<li class="page-item active"><span class="page-link">{{ page_obj.number }}</span></li>
{% if page_obj.has_next %}
<li class="page-item"><a class="page-link" href="?page={{ page_obj.next_page_number }}">{{ page_obj.next_page_number }}</a></li>
<li class="page-item"><a class="page-link" href="?page={{ page_obj.paginator.num_pages }}">&raquo;</a></li>
{% endif %}
</ul>
</nav>
{% endif %}
{% endblock %}