Aug 17 - Update with utilities management

This commit is contained in:
2026-08-17 12:53:24 -04:00
parent 790eb9894e
commit ceb1d1395d
17 changed files with 2375 additions and 6 deletions
+143
View File
@@ -0,0 +1,143 @@
{% extends "base.html" %}
{% block title %}Utility Bills{% endblock %}
{% block page_title %}Utility Bills{% endblock %}
{% block extra_css %}
.util-chip { font-size: 10px; font-weight: 700; padding: 2px 7px; border-radius: 10px; text-transform: uppercase; letter-spacing: .05em; }
{% endblock %}
{% block topbar_actions %}
<a href="{{ url_for('utilities.index') }}" class="btn btn-sm btn-outline-secondary" style="font-size:12px;">← Utilities</a>
<a href="{{ url_for('utilities.bill_new') }}" class="btn btn-sm btn-primary" style="font-size:12px;"><i class="bi bi-plus-lg me-1"></i><span class="btn-label">New Bill</span></a>
{% endblock %}
{% block content %}
<!-- Filters -->
<div class="pcard mb-3">
<form method="GET" class="row g-2 align-items-end">
<div class="col-6 col-md-3">
<label class="form-label fw-medium" style="font-size:12px;">Provider</label>
<select name="provider_id" class="form-select form-select-sm">
<option value="">All providers</option>
{% for p in providers %}
<option value="{{ p.id }}" {% if provider_id == p.id %}selected{% endif %}>{{ p.name }}</option>
{% endfor %}
</select>
</div>
<div class="col-6 col-md-2">
<label class="form-label fw-medium" style="font-size:12px;">Type</label>
<select name="utility_type" class="form-select form-select-sm">
<option value="">All types</option>
{% for key, meta in type_meta.items() %}
<option value="{{ key }}" {% if utility_type == key %}selected{% endif %}>{{ meta[0] }}</option>
{% endfor %}
</select>
</div>
<div class="col-6 col-md-2">
<label class="form-label fw-medium" style="font-size:12px;">Status</label>
<select name="status" class="form-select form-select-sm">
<option value="">All</option>
<option value="unpaid" {% if status == 'unpaid' %}selected{% endif %}>Unpaid</option>
<option value="overdue" {% if status == 'overdue' %}selected{% endif %}>Overdue</option>
<option value="paid" {% if status == 'paid' %}selected{% endif %}>Paid</option>
</select>
</div>
<div class="col-6 col-md-2">
<label class="form-label fw-medium" style="font-size:12px;">Year</label>
<select name="year" class="form-select form-select-sm">
<option value="">All years</option>
{% for y in years %}
<option value="{{ y }}" {% if year == y %}selected{% endif %}>{{ y }}</option>
{% endfor %}
</select>
</div>
<div class="col-12 col-md-3 d-flex gap-2">
<button type="submit" class="btn btn-sm btn-primary" style="font-size:12px;"><i class="bi bi-funnel me-1"></i>Filter</button>
<a href="{{ url_for('utilities.bills') }}" class="btn btn-sm btn-outline-secondary" style="font-size:12px;">Clear</a>
</div>
</form>
</div>
{% if bills %}
<div class="pcard p-0">
<div class="table-wrap">
<table class="pfm-table wide">
<thead>
<tr>
<th>Provider</th>
<th>Period</th>
<th class="d-mob-none">Due</th>
<th class="text-end">Amount</th>
<th class="text-end d-mob-none">Usage</th>
<th>Status</th>
<th class="text-end">Actions</th>
</tr>
</thead>
<tbody>
{% for b in bills %}
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<div style="width:28px;height:28px;border-radius:7px;background:{{ b.provider.color }}22;color:{{ b.provider.color }};display:flex;align-items:center;justify-content:center;font-size:14px;flex-shrink:0;">
<i class="bi {{ b.provider.icon }}"></i>
</div>
<div>
<a href="{{ url_for('utilities.provider_detail', id=b.provider_id) }}" style="font-size:13px;font-weight:600;text-decoration:none;color:inherit;">{{ b.provider.name }}</a>
<div style="font-size:11px;color:var(--muted);">{{ b.provider.type_label }}</div>
</div>
</div>
</td>
<td style="font-size:12px;">{{ b.period_label }}</td>
<td class="d-mob-none" style="font-size:12px;">{{ b.due_date.strftime('%b %d, %Y') if b.due_date else '—' }}</td>
<td class="text-end mono fw-bold" style="font-size:13px;">{{ b.amount | currency }}</td>
<td class="text-end mono d-mob-none" style="font-size:12px;">
{% if b.usage %}{{ '%.1f' | format(b.usage | float) }} <span style="color:var(--muted);">{{ b.usage_unit or '' }}</span>{% else %}—{% endif %}
</td>
<td>
{% if b.status == 'paid' %}
<span class="util-chip" style="background:#d1fae5;color:#065f46;">Paid</span>
{% elif b.status == 'overdue' %}
<span class="util-chip" style="background:#fee2e2;color:#991b1b;">{{ b.days_until_due | abs }}d late</span>
{% elif b.status == 'due_soon' %}
<span class="util-chip" style="background:#fef3c7;color:#92400e;">Due in {{ b.days_until_due }}d</span>
{% else %}
<span class="util-chip" style="background:#f1f5f9;color:#475569;">Unpaid</span>
{% endif %}
</td>
<td class="text-end">
{% if not b.is_paid %}
<a href="{{ url_for('utilities.bill_pay', id=b.id) }}" class="btn btn-sm btn-primary" style="font-size:11px;padding:3px 10px;">Pay</a>
{% endif %}
<a href="{{ url_for('utilities.bill_edit', id=b.id) }}" class="btn btn-sm btn-outline-secondary" style="font-size:11px;padding:3px 8px;"><i class="bi bi-pencil"></i></a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% if pagination.pages > 1 %}
<div class="d-flex justify-content-between align-items-center p-3" style="border-top:1px solid var(--border);font-size:12px;color:var(--muted);">
<span>Page {{ pagination.page }} of {{ pagination.pages }} &nbsp;·&nbsp; {{ ((pagination.page-1)*30)+1 }}{{ [pagination.page*30, pagination.total]|min }} of {{ pagination.total }}</span>
<div class="d-flex gap-2">
{% if pagination.has_prev %}
<a href="{{ url_for('utilities.bills', page=pagination.prev_num, provider_id=provider_id, utility_type=utility_type, status=status, year=year) }}" class="btn btn-sm btn-outline-secondary" style="font-size:11px;">← Prev</a>
{% endif %}
{% if pagination.has_next %}
<a href="{{ url_for('utilities.bills', page=pagination.next_num, provider_id=provider_id, utility_type=utility_type, status=status, year=year) }}" class="btn btn-sm btn-outline-secondary" style="font-size:11px;">Next →</a>
{% endif %}
</div>
</div>
{% endif %}
</div>
{% else %}
<div class="pcard text-center py-5">
<i class="bi bi-receipt text-muted" style="font-size:3rem;"></i>
<h5 class="mt-3 mb-1">No bills match</h5>
<p class="text-muted small mb-3">Try clearing the filters, or record a new bill.</p>
<a href="{{ url_for('utilities.bill_new') }}" class="btn btn-primary btn-sm">New Bill</a>
</div>
{% endif %}
{% endblock %}