v0.9.1
This commit is contained in:
+65
-4
@@ -126,6 +126,68 @@ function compressorQueuePanel(zone) {
|
||||
return `<div class="compressor-queue-panel"><div><strong>${esc(tr('zones.compressorQueueTitle'))}</strong><span>${esc(compressorPendingDescription(zone))}</span><small>${esc(tr('zones.compressorQueueUntil', {time:when}))}</small></div><button type="button" class="secondary" data-action="cancel-compressor-task" data-id="${esc(zone.id)}">${esc(tr('actions.cancel'))}</button></div>`;
|
||||
}
|
||||
|
||||
function compressorQueueRemainingText(until) {
|
||||
const timestamp = until ? new Date(until).getTime() : NaN;
|
||||
if (!Number.isFinite(timestamp)) return tr('zones.afterProtection');
|
||||
const seconds = Math.max(0, Math.ceil((timestamp - Date.now()) / 1000));
|
||||
return seconds > 0 ? tr('zones.queueRemaining', {time:formatExtendedCountdown(seconds)}) : tr('zones.queueReady');
|
||||
}
|
||||
|
||||
function renderCompressorQueueModal() {
|
||||
const list = $('#compressorQueueList');
|
||||
const countNode = $('#compressorQueueModalCount');
|
||||
const cancelAll = $('#compressorQueueCancelAll');
|
||||
if (!list || !countNode || !cancelAll) return;
|
||||
const pending = app.zones
|
||||
.filter(zone => !!zone.compressor_pending_action)
|
||||
.slice()
|
||||
.sort((a,b) => {
|
||||
const at = new Date(a.compressor_pending_until || 0).getTime();
|
||||
const bt = new Date(b.compressor_pending_until || 0).getTime();
|
||||
return (Number.isFinite(at) ? at : Number.MAX_SAFE_INTEGER) - (Number.isFinite(bt) ? bt : Number.MAX_SAFE_INTEGER);
|
||||
});
|
||||
countNode.textContent = String(pending.length);
|
||||
cancelAll.disabled = pending.length === 0;
|
||||
cancelAll.hidden = pending.length === 0;
|
||||
cancelAll.textContent = pending.length ? `${tr('zones.cancelAllQueued')} (${pending.length})` : tr('zones.cancelAllQueued');
|
||||
if (!pending.length) {
|
||||
list.innerHTML = `<div class="compressor-queue-empty"><span aria-hidden="true">✓</span><strong>${esc(tr('zones.queueEmptyTitle'))}</strong><p>${esc(tr('zones.queueEmptyText'))}</p></div>`;
|
||||
return;
|
||||
}
|
||||
list.innerHTML = pending.map((zone,index) => {
|
||||
const device = app.devices.find(item => item.id === zone.device_id);
|
||||
const until = zone.compressor_pending_until || zone.lockout_until;
|
||||
const group = groupControlForZone(zone);
|
||||
const source = group ? `${zoneControlOwnerLabel(zone)}: ${group.name}` : zoneControlOwnerLabel(zone);
|
||||
const exact = until ? dateTime(until) : tr('zones.afterProtection');
|
||||
return `<article class="compressor-queue-item">
|
||||
<div class="compressor-queue-index">${index + 1}</div>
|
||||
<div class="compressor-queue-item-main">
|
||||
<div class="compressor-queue-item-head"><div><span class="eyebrow">${esc(zone.name)}</span><h3>${esc(device?.name || tr('common.noDevice'))}</h3></div><span class="compressor-queue-reason">${esc(tr('zones.queueReasonProtection'))}</span></div>
|
||||
<p class="compressor-queue-command">${esc(compressorPendingDescription(zone))}</p>
|
||||
<div class="compressor-queue-meta">
|
||||
<span>${esc(tr('zones.queueSource', {source}))}</span>
|
||||
<span>${esc(tr('zones.compressorQueueUntil', {time:exact}))}</span>
|
||||
<strong data-compressor-queue-countdown="${esc(until || '')}">${esc(compressorQueueRemainingText(until))}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="secondary compressor-queue-cancel" data-action="cancel-compressor-task" data-id="${esc(zone.id)}">${esc(tr('actions.cancel'))}</button>
|
||||
</article>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function updateCompressorQueueCountdowns() {
|
||||
$$('[data-compressor-queue-countdown]').forEach(node => {
|
||||
node.textContent = compressorQueueRemainingText(node.dataset.compressorQueueCountdown);
|
||||
});
|
||||
}
|
||||
|
||||
function openCompressorQueueDialog() {
|
||||
renderCompressorQueueModal();
|
||||
openDialog('compressorQueueDialog');
|
||||
updateCompressorQueueCountdowns();
|
||||
}
|
||||
|
||||
function zoneControlOwnerLabel(zone) {
|
||||
const owner=zone.control_owner || (zone.device_manual_override ? 'direct_manual' : (zone.local_thermostat_power != null ? 'local_thermostat' : 'automation'));
|
||||
const source=zone.control_source || '';
|
||||
@@ -265,10 +327,9 @@ function renderZones() {
|
||||
const dashboardCount = $('#dashboardZoneCount');
|
||||
if (dashboardCount) dashboardCount.textContent = String(app.zones.length);
|
||||
const pendingCount = app.zones.filter(zone => !!zone.compressor_pending_action).length;
|
||||
$$('[data-compressor-cancel-all]').forEach(cancelAll => {
|
||||
cancelAll.hidden = pendingCount === 0;
|
||||
cancelAll.textContent = pendingCount ? `${tr('zones.cancelAllQueued')} (${pendingCount})` : tr('zones.cancelAllQueued');
|
||||
});
|
||||
$$('[data-compressor-queue-count]').forEach(node => { node.textContent = String(pendingCount); });
|
||||
$$('.queue-overview-button').forEach(button => button.classList.toggle('has-items', pendingCount > 0));
|
||||
renderCompressorQueueModal();
|
||||
}
|
||||
|
||||
function groupZones(group) {
|
||||
|
||||
+2
-1
@@ -144,8 +144,9 @@ document.addEventListener('click', async event => {
|
||||
if(!Number.isFinite(value) || value<8 || value>30) return toast(tr('groups.customTemperatureRange'),true);
|
||||
return sendGroupControl(button.dataset.id,{preset:'custom',setpoint:Math.round(value*10)/10});
|
||||
}
|
||||
if (action === 'open-compressor-queue') return openCompressorQueueDialog();
|
||||
if (action === 'cancel-compressor-task') return cancelCompressorTask(button.dataset.id);
|
||||
if (action === 'cancel-all-compressor-tasks') return cancelAllCompressorTasks();
|
||||
if (action === 'cancel-all-compressor-tasks') { if (!confirm(tr('zones.queueCancelAllConfirm', {count:app.zones.filter(zone => !!zone.compressor_pending_action).length}))) return; return cancelAllCompressorTasks(); }
|
||||
if (action === 'zone-device-power') return sendZoneLocalPower(button.dataset.id, button.dataset.value === 'true');
|
||||
if (action === 'zone-open-temporary') return populateTemporaryThermostat(button.dataset.id);
|
||||
if (action === 'zone-temperature') { const zone=app.zones.find(v=>v.id===button.dataset.id); if(zone) { const base=Number(zone.manual_setpoint ?? zone.effective_setpoint ?? zone.setpoint); queueZoneTemperature(zone, base+Number(button.dataset.delta)); } return; }
|
||||
|
||||
@@ -17,4 +17,5 @@ async function startApplication() {
|
||||
|
||||
setInterval(updateLocalThermostatCountdowns, 1000);
|
||||
setInterval(updateTemporaryThermostatCountdowns, 1000);
|
||||
setInterval(updateCompressorQueueCountdowns, 1000);
|
||||
startApplication().catch(error => console.error('Application startup failed:', error));
|
||||
|
||||
Reference in New Issue
Block a user