ixg_platform/templates/portal/dashboard.html
ixgadmin cdefbd010c Serve document and certificate files through a permission-checked download
Media was unreachable from either portal, which is why the certificate button on
the buyer compliance page returned page-not-found and no document row had
anything to click. The cause was structural: the portal and ops hosts proxy
location / to the app with a path prefix, so a stored /media/... URL arrives as
/portal/media/... and matches no route.

Add a download view per portal behind the same role gate as the page that links
to it — buyer-visible documents and published certificates for buyers, anything
the ops list shows for operations — resolving the stored path safely under
MEDIA_ROOT so a traversal or an absolute path is refused. Point the seven
templates that list files at the new routes.

Also add a portal-wide render sweep: nothing in the suite was fetching the ops
or portal pages, so a view could lose its template_name and 500 in front of a
user with every test still green.
2026-09-17 12:44:52 +00:00

74 lines
No EOL
2.6 KiB
HTML

{% extends 'portal/base_portal.html' %}
{% block title %}Dashboard - IXG Buyer Portal{% endblock %}
{% block content %}
<div class="page-header d-flex justify-content-between align-items-center">
<h2>Buyer Dashboard</h2>
<span class="text-secondary">Batches available: <strong>{{ total_batches }}</strong></span>
</div>
<div class="row g-3 mb-4">
<div class="col-md-4">
<div class="stat-card">
<div class="stat-value text-primary">{{ total_batches }}</div>
<div class="stat-label">Total Batches</div>
</div>
</div>
<div class="col-md-4">
<div class="stat-card">
<div class="stat-value text-success">{{ active_shipments }}</div>
<div class="stat-label">Active Shipments</div>
</div>
</div>
<div class="col-md-4">
<div class="stat-card">
<div class="stat-value text-info">{{ documents|length }}</div>
<div class="stat-label">Recent Documents</div>
</div>
</div>
</div>
<div class="row g-3">
<div class="col-md-7">
<div class="stat-card">
<h6 class="mb-3">Recent Batches</h6>
<table class="table table-sm mb-0">
<thead><tr><th>Reference</th><th>Grade</th><th>Status</th><th>Harvest Date</th></tr></thead>
<tbody>
{% for b in recent_batches %}
<tr>
<td><a href="{% url 'portal:batch_detail' b.pk %}">{{ b.batch_reference }}</a></td>
<td>{{ b.grade|default:'-' }}</td>
<td><span class="badge bg-secondary badge-status">{{ b.get_status_display }}</span></td>
<td>{{ b.harvest_date|date:'d/m/Y'|default:'-' }}</td>
</tr>
{% empty %}
<tr><td colspan="4" class="text-muted">No batches</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="col-md-5">
<div class="stat-card mb-3">
<h6 class="mb-3">Recent Documents</h6>
{% for d in documents %}
<div class="border-bottom pb-2 mb-2">
<a href="{% url 'portal:document_download' d.id %}" target="_blank"><strong>{{ d.file_name }}</strong></a><br>
<span class="text-muted small">{{ d.get_doc_type_display }} · {{ d.uploaded_at|date:'d/m/Y' }}</span>
</div>
{% empty %}
<p class="text-muted mb-0">No documents available</p>
{% endfor %}
</div>
{% if subscription %}
<div class="stat-card">
<h6 class="mb-2">Subscription</h6>
<span class="badge {% if subscription.status == 'active' %}bg-success{% else %}bg-warning text-dark{% endif %}">{{ subscription.get_status_display }}</span>
<span class="text-muted ms-2">{{ subscription.get_plan_display }}</span>
</div>
{% endif %}
</div>
</div>
{% endblock %}