An unquoted attribute value ends at the first whitespace, so ``class=badge bg-danger`` is read by every browser as ``class="badge"`` plus two meaningless attributes. Bootstrap was loading perfectly well; the markup simply never asked for the styles. Across the ops portal that meant buttons rendered as plain links, status badges were invisible (white text on a white background: a failed result showed as nothing at all), the active sidebar item never highlighted, and the flex utility classes did nothing. The buyer portal templates quote their attributes, which is why only the ops portal looked unstyled. 346 attributes across 20 templates, rewritten mechanically with a conservative transform that handles the three shapes in use — a Django tag as the value, a multi-token class list, and a trailing simple conditional (the active-nav highlight) — and leaves anything it does not recognise exactly as it was.
141 lines
6.6 KiB
HTML
141 lines
6.6 KiB
HTML
{% extends 'ops/base_ops.html' %}
|
|
{% block title %}EUDR Case - IXG Operations{% endblock %}
|
|
{% block content %}
|
|
|
|
<div class="page-header d-flex justify-content-between align-items-center">
|
|
<h2>EUDR Case: {{ case.batch.batch_reference }}</h2>
|
|
<a href="{% url 'ops:eudr_list' %}" class="btn btn-sm btn-outline-secondary"><i class="bi bi-arrow-left"></i> Back</a>
|
|
</div>
|
|
|
|
{% if messages %}
|
|
{% for m in messages %}<div class="alert alert-{{ m.tags }} py-2">{{ m }}</div>{% endfor %}
|
|
{% endif %}
|
|
|
|
<div class="row g-3">
|
|
<div class="col-md-8">
|
|
<div class="stat-card mb-3">
|
|
<h6 class="mb-3">Completeness Check</h6>
|
|
{% if case.completeness_results %}
|
|
<table class="table table-sm mb-0">
|
|
<thead><tr><th>Check</th><th>Status</th><th>Detail</th></tr></thead>
|
|
<tbody>
|
|
{% for key, item in case.completeness_results.items %}
|
|
<tr>
|
|
<td>{{ key }}</td>
|
|
<td>{% if item.pass %}<span class="badge bg-success">Pass</span>{% else %}<span class="badge bg-danger">Fail</span>{% endif %}</td>
|
|
<td>{{ item.detail }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{% else %}
|
|
<p class="text-muted mb-0">Not yet run.</p>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="stat-card mb-3">
|
|
<h6 class="mb-3">Intake Lineage</h6>
|
|
{% if lineage %}
|
|
<table class="table table-sm mb-0">
|
|
<thead><tr><th>Intake</th><th>Date</th><th>Weight (kg)</th><th>Zone</th><th>Zone Status</th><th>Collector</th></tr></thead>
|
|
<tbody>
|
|
{% for link in lineage %}
|
|
<tr>
|
|
<td>{{ link.harvest_intake.intake_reference }}</td>
|
|
<td>{{ link.harvest_intake.intake_date|date:'d/m/Y'|default:'-' }}</td>
|
|
<td>{% if link.weight_allocated_kg %}{{ link.weight_allocated_kg }}{% elif link.harvest_intake %}{{ link.harvest_intake.weight_kg }}{% else %}-{% endif %}</td>
|
|
<td>{{ link.harvest_intake.origin_zone.zone_code|default:'-' }}</td>
|
|
<td>{% if link.harvest_intake.origin_zone %}<span class="badge" {% if link.harvest_intake.origin_zone.verification_status == 'verified' %}bg-success{% elif link.harvest_intake.origin_zone.verification_status == 'rejected' %}bg-danger{% else %}bg-warning text-dark{% endif %}>{{ link.harvest_intake.origin_zone.get_verification_status_display }}</span>{% else %}-{% endif %}</td>
|
|
<td>{{ link.harvest_intake.collector_group.name|default:'-' }}</td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr><td colspan="6" class="text-muted">No intakes linked. Record intake lineage via API/admin.</td></tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{% else %}
|
|
<p class="text-muted mb-0">No intake lineage recorded for this batch.</p>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="stat-card mb-3">
|
|
<h6 class="mb-3">Risk Assessments</h6>
|
|
{% for r in risk_assessments %}
|
|
<div class="border-bottom pb-2 mb-2">
|
|
<span class="badge" {% if r.risk_level == 'high' %}bg-danger{% elif r.risk_level == 'low' %}bg-success{% else %}bg-info text-dark{% endif %}>{{ r.get_risk_level_display }}</span>
|
|
<span class="text-muted small">by {{ r.assessed_by.full_name|default:'-' }} · {{ r.assessed_at|date:'d/m/Y H:i' }}</span>
|
|
<div class="small mt-1">{{ r.basis }}</div>
|
|
</div>
|
|
{% empty %}
|
|
<p class="text-muted mb-0">No risk assessment recorded.</p>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<div class="stat-card mb-3">
|
|
<h6 class="mb-3">Mitigation Actions</h6>
|
|
{% for m in mitigation_actions %}
|
|
<div class="border-bottom pb-2 mb-2">
|
|
<span class="badge" {% if m.status == 'verified' %}bg-success{% elif m.status == 'completed' %}bg-primary{% else %}bg-warning text-dark{% endif %}>{{ m.get_status_display }}</span>
|
|
<span class="small">{{ m.action }} <span class="text-muted">(owner: {{ m.owner|default:'-' }}{% if m.due_date %}, due {{ m.due_date|date:'d/m/Y' }}{% endif %})</span></span>
|
|
</div>
|
|
{% empty %}
|
|
<p class="text-muted mb-0">No mitigation actions.</p>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<div class="stat-card">
|
|
<h6 class="mb-3">External Submission (DDS) References</h6>
|
|
{% for s in statement_references %}
|
|
<div class="border-bottom pb-2 mb-2">
|
|
<span class="badge bg-secondary">{{ s.reconciliation_status }}</span>
|
|
<span class="small">Ref: <code>{{ s.dds_reference }}</code> · {{ s.submission_date|date:'d/m/Y'|default:'-' }} · by {{ s.submitted_by.full_name|default:'-' }}</span>
|
|
</div>
|
|
{% empty %}
|
|
<p class="text-muted mb-0">No external submission recorded. Submissions are manual.</p>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-4">
|
|
<div class="stat-card mb-3">
|
|
<h6 class="mb-3">Case Controls</h6>
|
|
<form method="post" class="mb-2">
|
|
{% csrf_token %}
|
|
<input type="hidden" name="action" value="check">
|
|
<button type="submit" class="btn btn-outline-primary w-100"><i class="bi bi-check2-square"></i> Run Completeness Check</button>
|
|
</form>
|
|
<form method="post" class="mb-2">
|
|
{% csrf_token %}
|
|
<input type="hidden" name="action" value="assess_risk">
|
|
<button type="submit" class="btn btn-outline-warning w-100"><i class="bi bi-activity"></i> Run Risk Assessment</button>
|
|
</form>
|
|
<form method="post" class="mb-2">
|
|
{% csrf_token %}
|
|
<input type="hidden" name="action" value="approve">
|
|
<button type="submit" class="btn btn-success w-100"><i class="bi bi-check-circle"></i> Approve Case</button>
|
|
</form>
|
|
<form method="post">
|
|
{% csrf_token %}
|
|
<input type="hidden" name="action" value="reject">
|
|
<label class="small">Reject reason</label>
|
|
<input type="text" name="reason" class="form-control form-control-sm mb-2" required>
|
|
<button type="submit" class="btn btn-outline-danger w-100"><i class="bi bi-x-circle"></i> Reject Case</button>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="stat-card">
|
|
<h6 class="mb-3">Record External Submission</h6>
|
|
<form method="post">
|
|
{% csrf_token %}
|
|
<input type="hidden" name="action" value="record_submission">
|
|
<label class="small">DDS reference</label>
|
|
<input type="text" name="dds_reference" class="form-control form-control-sm mb-2" placeholder="e.g. DD-2026-00001" required>
|
|
<label class="small">Submission date</label>
|
|
<input type="date" name="submission_date" class="form-control form-control-sm mb-2">
|
|
<button type="submit" class="btn btn-primary w-100"><i class="bi bi-send"></i> Record Submission</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% endblock %}
|