Template
## Summary - Closes #5 - Optional `shop`, `pos_sync`, `events`, and `shipping` apps gated by `FEATURE_*` flags - Deps match the catalog: shop/events need email + Stripe; POS/shipping need shop - Portal inventory, POS webhooks, capacity tickets, EasyPost/Pirate Ship shipping ## Test plan - [ ] `manage.py test` (152 passed locally) - [ ] Shop cart + paid order decrements stocked inventory - [ ] POS inbound webhook decrements SKU; paid order queues outbound reserve - [ ] Event capacity blocks overbook; paid order emails ticket codes - [ ] Shipping stub label + Pirate Ship CSV of unshipped paid orders - [ ] `validate-env.sh` rejects shop without email/payments, POS/shipping without shop Reviewed-on: #6
108 lines
3.7 KiB
HTML
108 lines
3.7 KiB
HTML
{% extends "portal_base.html" %}
|
|
{% block title %}Dashboard · Portal{% endblock %}
|
|
{% block topbar_title %}Dashboard{% endblock %}
|
|
{% block portal_content %}
|
|
<div class="stat-row">
|
|
<div class="stat-card">
|
|
<div class="label">New leads</div>
|
|
<div class="value">{{ lead_count }}</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="label">Open pipeline</div>
|
|
<div class="value">{{ open_pipeline }}</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="label">Mailing list</div>
|
|
<div class="value">{{ contact_count }}</div>
|
|
</div>
|
|
{% if 'social' in enabled_features %}
|
|
<div class="stat-card">
|
|
<div class="label">Scheduled posts</div>
|
|
<div class="value">{{ scheduled_posts|default:0 }}</div>
|
|
</div>
|
|
{% endif %}
|
|
{% if 'payments' in enabled_features %}
|
|
<div class="stat-card">
|
|
<div class="label">Open invoices</div>
|
|
<div class="value">{{ open_invoices|default:0 }}</div>
|
|
</div>
|
|
{% endif %}
|
|
{% if 'shop' in enabled_features %}
|
|
<div class="stat-card">
|
|
<div class="label">Open shop orders</div>
|
|
<div class="value">{{ open_shop_orders|default:0 }}</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="label">Sales (30 days)</div>
|
|
<div class="value">{{ shop_sales_30d|default:0 }}</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="label">Shop revenue (30 days)</div>
|
|
<div class="value" style="font-size:22px">{{ shop_revenue_30d|default:0 }}</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="label">Low stock</div>
|
|
<div class="value">{{ low_stock_products|default:0 }}</div>
|
|
</div>
|
|
{% endif %}
|
|
{% if 'events' in enabled_features %}
|
|
<div class="stat-card">
|
|
<div class="label">Upcoming events</div>
|
|
<div class="value">{{ upcoming_events|default:0 }}</div>
|
|
</div>
|
|
{% endif %}
|
|
{% if 'shipping' in enabled_features %}
|
|
<div class="stat-card">
|
|
<div class="label">Unshipped orders</div>
|
|
<div class="value">{{ unshipped_orders|default:0 }}</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="split">
|
|
<div class="panel">
|
|
<div class="panel-h">
|
|
<h2>Recent leads</h2>
|
|
<a class="btn btn-sm btn-ghost" href="{% url 'leads:list' %}">View all</a>
|
|
</div>
|
|
<div class="panel-b" style="padding:0">
|
|
<table class="table">
|
|
<thead>
|
|
<tr><th>Name</th><th>Source</th><th>Status</th></tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for lead in recent_leads %}
|
|
<tr>
|
|
<td><a href="{% url 'leads:detail' lead.pk %}">{{ lead.contact }}</a></td>
|
|
<td>{% if lead.attribution %}{{ lead.attribution.utm_source|default:"direct" }}{% if lead.attribution.utm_campaign %} / {{ lead.attribution.utm_campaign }}{% endif %}{% else %}—{% endif %}</td>
|
|
<td><span class="badge badge-{{ lead.status }}">{{ lead.get_status_display }}</span></td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr><td colspan="3" class="empty-state">No leads yet.</td></tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
{% if 'email_sms' in enabled_features %}
|
|
<div class="panel">
|
|
<div class="panel-h">
|
|
<h2>Upcoming outreach</h2>
|
|
<a class="btn btn-sm btn-ghost" href="{% url 'email_sms:campaign_list' %}">Compose</a>
|
|
</div>
|
|
<div class="panel-b">
|
|
{% for campaign in upcoming_campaigns %}
|
|
<p style="margin:0 0 12px;font-size:14px">
|
|
<strong><a href="{% url 'email_sms:campaign_detail' campaign.pk %}">{{ campaign.name }}</a></strong>
|
|
— {{ campaign.get_channel_display }} · {{ campaign.get_status_display }}
|
|
{% if campaign.scheduled_for %} · {{ campaign.scheduled_for }}{% endif %}
|
|
</p>
|
|
{% empty %}
|
|
<p class="empty-state" style="margin:0">No campaigns yet.</p>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endblock %}
|