161 lines
7.8 KiB
HTML
161 lines
7.8 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}{{ 'Edit' if editing else 'Add' }} Meter - HAMeter{% endblock %}
|
|
{% block page_title %}{{ 'Edit' if editing else 'Add' }} Meter{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="config-form-container">
|
|
<form id="meter-form" onsubmit="return false;">
|
|
<div class="form-group">
|
|
<label for="meter-id">Meter ID (ERT Serial Number) <span class="required">*</span></label>
|
|
<input type="number" id="meter-id" value="{{ meter.id if meter else prefill_id|default('', true) }}" {{ 'readonly' if editing }} required>
|
|
{% if editing %}<small>Meter ID cannot be changed after creation.</small>{% endif %}
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="meter-protocol">Protocol <span class="required">*</span></label>
|
|
<select id="meter-protocol">
|
|
{% for p in protocols %}
|
|
<option value="{{ p }}" {{ 'selected' if (meter and meter.protocol == p) or (not meter and prefill_protocol|default('', true) == p) }}>{{ p }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="meter-name">Name <span class="required">*</span></label>
|
|
<input type="text" id="meter-name" value="{{ meter.name if meter else '' }}" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="meter-device-class">Device Class</label>
|
|
<select id="meter-device-class" onchange="onDeviceClassChange()">
|
|
<option value="" {{ 'selected' if not meter or not meter.device_class }}>None</option>
|
|
<option value="energy" {{ 'selected' if meter and meter.device_class == 'energy' }}>Energy (Electric)</option>
|
|
<option value="gas" {{ 'selected' if meter and meter.device_class == 'gas' }}>Gas</option>
|
|
<option value="water" {{ 'selected' if meter and meter.device_class == 'water' }}>Water</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="meter-unit">Unit of Measurement</label>
|
|
<input type="text" id="meter-unit" value="{{ meter.unit_of_measurement if meter else '' }}" placeholder="Auto-set by device class">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="meter-icon">Icon</label>
|
|
<input type="text" id="meter-icon" value="{{ meter.icon if meter else '' }}" placeholder="e.g. mdi:flash">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="meter-state-class">State Class</label>
|
|
<select id="meter-state-class">
|
|
<option value="total_increasing" {{ 'selected' if not meter or meter.state_class == 'total_increasing' }}>total_increasing</option>
|
|
<option value="measurement" {{ 'selected' if meter and meter.state_class == 'measurement' }}>measurement</option>
|
|
<option value="total" {{ 'selected' if meter and meter.state_class == 'total' }}>total</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="meter-multiplier">Calibration Multiplier</label>
|
|
<input type="number" id="meter-multiplier" step="any" value="{{ meter.multiplier if meter else '1.0' }}">
|
|
<small>Use <a href="/calibration">Calibration</a> to calculate this value.</small>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>Rate Components (Cost Factors)</label>
|
|
<small>Add rate components from your utility bill to track costs.</small>
|
|
<div id="cost-factors-list">
|
|
{% if meter and meter.cost_factors %}
|
|
{% for cf in meter.cost_factors %}
|
|
<div class="cost-factor-row" data-index="{{ loop.index0 }}">
|
|
<input type="text" class="cf-name" value="{{ cf.name }}" placeholder="Name (e.g. Generation)">
|
|
<input type="number" class="cf-rate" step="any" value="{{ cf.rate }}" placeholder="Rate">
|
|
<select class="cf-type">
|
|
<option value="per_unit" {{ 'selected' if cf.type == 'per_unit' }}>Per Unit</option>
|
|
<option value="fixed" {{ 'selected' if cf.type == 'fixed' }}>Fixed</option>
|
|
</select>
|
|
<button type="button" class="btn btn-icon" onclick="removeCostFactor(this)" title="Remove">×</button>
|
|
</div>
|
|
{% endfor %}
|
|
{% endif %}
|
|
</div>
|
|
<button type="button" class="btn btn-secondary btn-sm" onclick="addCostFactor()" style="margin-top: 0.5rem;">+ Add Rate Component</button>
|
|
</div>
|
|
|
|
<div class="form-actions">
|
|
<a href="/config/meters" class="btn btn-secondary">Cancel</a>
|
|
<button type="button" class="btn btn-primary" onclick="saveMeter()">Save</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<script>
|
|
function onDeviceClassChange() {
|
|
const dc = document.getElementById('meter-device-class').value;
|
|
if (!dc) return;
|
|
fetch('/api/meter_defaults/' + dc)
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
if (data.unit) document.getElementById('meter-unit').value = data.unit;
|
|
if (data.icon) document.getElementById('meter-icon').value = data.icon;
|
|
});
|
|
}
|
|
|
|
function addCostFactor(name, rate, type) {
|
|
const list = document.getElementById('cost-factors-list');
|
|
const row = document.createElement('div');
|
|
row.className = 'cost-factor-row';
|
|
row.innerHTML =
|
|
'<input type="text" class="cf-name" value="' + (name || '') + '" placeholder="Name (e.g. Generation)">' +
|
|
'<input type="number" class="cf-rate" step="any" value="' + (rate || '') + '" placeholder="Rate">' +
|
|
'<select class="cf-type">' +
|
|
'<option value="per_unit"' + (type === 'fixed' ? '' : ' selected') + '>Per Unit</option>' +
|
|
'<option value="fixed"' + (type === 'fixed' ? ' selected' : '') + '>Fixed</option>' +
|
|
'</select>' +
|
|
'<button type="button" class="btn btn-icon" onclick="removeCostFactor(this)" title="Remove">×</button>';
|
|
list.appendChild(row);
|
|
}
|
|
|
|
function removeCostFactor(btn) {
|
|
btn.closest('.cost-factor-row').remove();
|
|
}
|
|
|
|
function collectCostFactors() {
|
|
const rows = document.querySelectorAll('.cost-factor-row');
|
|
const factors = [];
|
|
rows.forEach(function(row) {
|
|
const name = row.querySelector('.cf-name').value.trim();
|
|
const rate = parseFloat(row.querySelector('.cf-rate').value);
|
|
const type = row.querySelector('.cf-type').value;
|
|
if (name && !isNaN(rate)) {
|
|
factors.push({ name: name, rate: rate, type: type });
|
|
}
|
|
});
|
|
return factors;
|
|
}
|
|
|
|
async function saveMeter() {
|
|
const data = {
|
|
id: parseInt(document.getElementById('meter-id').value),
|
|
protocol: document.getElementById('meter-protocol').value,
|
|
name: document.getElementById('meter-name').value,
|
|
device_class: document.getElementById('meter-device-class').value,
|
|
unit_of_measurement: document.getElementById('meter-unit').value,
|
|
icon: document.getElementById('meter-icon').value,
|
|
state_class: document.getElementById('meter-state-class').value,
|
|
multiplier: parseFloat(document.getElementById('meter-multiplier').value) || 1.0,
|
|
cost_factors: collectCostFactors(),
|
|
};
|
|
|
|
const editing = {{ 'true' if editing else 'false' }};
|
|
const url = editing ? '/api/config/meters/' + data.id : '/api/config/meters';
|
|
const method = editing ? 'PUT' : 'POST';
|
|
|
|
const resp = await fetch(url, {
|
|
method: method,
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify(data),
|
|
});
|
|
const res = await resp.json();
|
|
if (res.ok) {
|
|
showToast('Meter saved', 'success');
|
|
setTimeout(() => window.location.href = '/config/meters', 500);
|
|
} else {
|
|
showToast(res.error || 'Save failed', 'error');
|
|
}
|
|
}
|
|
</script>
|
|
{% endblock %}
|