Files
Personal-Finance-Management/app/templates/utilities/bill_form.html
T

149 lines
6.7 KiB
HTML

{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block page_title %}{{ title }}{% endblock %}
{% block topbar_actions %}
<a href="{{ url_for('utilities.bills') }}" class="btn btn-sm btn-outline-secondary" style="font-size:12px;">← Bills</a>
{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-12 col-md-9 col-lg-7">
<div class="pcard">
<form method="POST" novalidate>
{{ form.hidden_tag() }}
<div class="mb-3">
{{ form.provider_id.label(class="form-label fw-medium", style="font-size:13px;") }}
{{ form.provider_id(class="form-select" + (" is-invalid" if form.provider_id.errors else ""), id="providerSelect") }}
{% for e in form.provider_id.errors %}<div class="invalid-feedback">{{ e }}</div>{% endfor %}
</div>
<div class="row g-3 mb-3">
<div class="col-6">
{{ form.period_start.label(class="form-label fw-medium", style="font-size:13px;") }}
{{ form.period_start(class="form-control" + (" is-invalid" if form.period_start.errors else "")) }}
{% for e in form.period_start.errors %}<div class="invalid-feedback">{{ e }}</div>{% endfor %}
</div>
<div class="col-6">
{{ form.period_end.label(class="form-label fw-medium", style="font-size:13px;") }}
{{ form.period_end(class="form-control" + (" is-invalid" if form.period_end.errors else "")) }}
{% for e in form.period_end.errors %}<div class="invalid-feedback">{{ e }}</div>{% endfor %}
</div>
</div>
<div class="row g-3 mb-4">
<div class="col-6">
{{ form.amount.label(class="form-label fw-medium", style="font-size:13px;") }}
<div class="input-group">
<span class="input-group-text" style="font-size:13px;">{{ current_user.currency_symbol }}</span>
{{ form.amount(class="form-control" + (" is-invalid" if form.amount.errors else ""), placeholder="0.00", id="amountInput") }}
</div>
{% for e in form.amount.errors %}<div class="text-danger" style="font-size:12px;">{{ e }}</div>{% endfor %}
</div>
<div class="col-6">
{{ form.due_date.label(class="form-label fw-medium", style="font-size:13px;") }}
{{ form.due_date(class="form-control") }}
</div>
</div>
<!-- Usage -->
<div id="usageBlock" style="border-top:1px solid var(--border);padding-top:16px;">
<div class="d-flex justify-content-between align-items-center mb-3">
<label class="form-label fw-medium mb-0" style="font-size:13px;">Consumption <span class="text-muted" style="font-weight:400;">— optional</span></label>
<span class="badge" style="background:#f1f5f9;color:#475569;font-size:11px;" id="unitBadge"></span>
</div>
<div class="row g-3 mb-2">
<div class="col-12 col-sm-4">
{{ form.usage.label(class="form-label fw-medium", style="font-size:13px;") }}
{{ form.usage(class="form-control", placeholder="0.000", id="usageInput") }}
</div>
<div class="col-6 col-sm-4">
{{ form.meter_start.label(class="form-label fw-medium", style="font-size:13px;") }}
{{ form.meter_start(class="form-control", placeholder="Previous reading", id="meterStart") }}
</div>
<div class="col-6 col-sm-4">
{{ form.meter_end.label(class="form-label fw-medium", style="font-size:13px;") }}
{{ form.meter_end(class="form-control" + (" is-invalid" if form.meter_end.errors else ""), placeholder="Current reading", id="meterEnd") }}
{% for e in form.meter_end.errors %}<div class="invalid-feedback">{{ e }}</div>{% endfor %}
</div>
</div>
<small class="text-muted" style="font-size:11px;">
Enter usage directly, or both meter readings — readings win and fill in usage automatically.
</small>
<div class="mt-3 p-2" id="rateHint" style="display:none;background:#f8fafc;border-radius:8px;font-size:12px;">
<i class="bi bi-calculator me-1"></i>Unit rate: <span class="mono fw-bold" id="rateValue"></span>
</div>
</div>
<div class="mb-4 mt-3">
{{ form.notes.label(class="form-label fw-medium", style="font-size:13px;") }}
{{ form.notes(class="form-control", rows=2, placeholder="Rate plan, unusual charges, meter notes…") }}
</div>
<div class="d-flex gap-2 flex-wrap">
{{ form.submit(class="btn btn-primary") }}
{% if not bill %}
<button type="submit" name="pay_now" value="yes" class="btn btn-outline-primary">Save &amp; Mark Paid</button>
{% endif %}
<a href="{{ url_for('utilities.bills') }}" class="btn btn-outline-secondary">Cancel</a>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
(function () {
// provider id -> usage unit, so the form can label and validate consumption
const UNITS = {{ provider_units | tojson }};
const sym = {{ current_user.currency_symbol | tojson }};
const sel = document.getElementById('providerSelect');
const block = document.getElementById('usageBlock');
const badge = document.getElementById('unitBadge');
const usage = document.getElementById('usageInput');
const mStart = document.getElementById('meterStart');
const mEnd = document.getElementById('meterEnd');
const amount = document.getElementById('amountInput');
const hint = document.getElementById('rateHint');
const rateVal = document.getElementById('rateValue');
function unit() { return UNITS[sel.value] || ''; }
function syncUnit() {
const u = unit();
badge.textContent = u || 'no usage tracked';
block.style.opacity = u ? '1' : '.55';
}
function syncUsage() {
const a = parseFloat(mStart.value), b = parseFloat(mEnd.value);
if (!isNaN(a) && !isNaN(b) && b >= a) usage.value = (b - a).toFixed(3);
syncRate();
}
function syncRate() {
const amt = parseFloat(amount.value), u = parseFloat(usage.value);
if (!isNaN(amt) && !isNaN(u) && u > 0) {
rateVal.textContent = sym + (amt / u).toFixed(4) + ' per ' + (unit() || 'unit');
hint.style.display = 'block';
} else {
hint.style.display = 'none';
}
}
sel.addEventListener('change', syncUnit);
[mStart, mEnd].forEach(el => el.addEventListener('input', syncUsage));
[amount, usage].forEach(el => el.addEventListener('input', syncRate));
syncUnit();
syncRate();
})();
</script>
{% endblock %}