This commit is contained in:
Mateusz Gruszczyński committed 2026-10-21 11:27:44 +02:00
1 parent bb5caa769f
commit a679eb5747
25 files changed
+484 -78

No files matched your search

+60 -3
View File
@@ -331,6 +331,59 @@ function zoneControlSourceLabel(source) {
})[source] || source || tr('zones.sourceUnavailable');
}
function zoneTemperatureExplanation(zone) {
const gree = fmtTemp(zone.device_temperature);
const room = fmtTemp(zone.external_temperature);
const used = fmtTemp(zone.current_temperature);
const source = zone.control_temperature_source;
if (source === 'combined' && zone.device_temperature != null && zone.external_temperature != null) {
const roomWeight = Math.round(Math.max(0, Math.min(1, Number(zone.external_sensor_weight ?? 0.4))) * 100);
return tr('zones.temperatureFormula', { gree, room, used, greeWeight: 100 - roomWeight, roomWeight });
}
if (source === 'external') {
return tr('zones.temperatureFromRoom', { room, gree });
}
if (source === 'device') {
return tr('zones.temperatureFromGree', { gree });
}
if (source === 'device_discrepancy_fallback') {
return tr('zones.temperatureDiscrepancy', { gree, room, limit: fmtTemp(zone.max_sensor_difference) });
}
if (source === 'device_fallback') {
const reason = zone.room_sensor_status === 'stale'
? tr('zones.sensorStale')
: tr('zones.sensorUnavailable');
return tr('zones.temperatureFallback', { gree, reason });
}
return tr('zones.temperatureUnavailable');
}
function zoneTemperatureHelp(zone) {
const explanation = zoneTemperatureExplanation(zone);
const guarded = zone.heating_sensor_guard_active === true;
const guardDetail = guarded ? tr('zones.heatGuardExplanation') : '';
const sensorStatus = zoneRoomSensorStatusText(zone);
const tooltip = [explanation, sensorStatus, guardDetail].filter(Boolean).join(' ');
return `<details class="zone-temperature-help"><summary title="${esc(tooltip)}" aria-label="${esc(tr('zones.temperatureHelp'))}">${uiIcon('system-info')}<span>${esc(tr('zones.temperatureHelp'))}</span></summary><div class="zone-temperature-help-content">${esc(explanation)}${sensorStatus ? `<p>${esc(sensorStatus)}</p>` : ''}${guarded ? `<p class="zone-temperature-guard-detail">${esc(guardDetail)}</p>` : ''}</div></details>`;
}
function zoneRoomSensorStatusText(zone) {
if (!zone || zone.sensor_source === 'device') return '';
const key = {
ok: 'zones.sensorStatusOk',
stale: 'zones.sensorStatusStale',
error: 'zones.sensorStatusError',
discrepancy: 'zones.sensorStatusDiscrepancy',
}[zone.room_sensor_status];
return key ? tr('zones.sensorStatusLabel', { status: tr(key) }) : '';
}
function zoneHeatingGuardNotice(zone, device) {
if (zone.heating_sensor_guard_active !== true) return '';
const pending = device?.power && ['heat', 'auto'].includes(device?.mode);
return `<p class="zone-heating-sensor-guard" role="status">${uiIcon('system-info')}<span><strong>${esc(tr('zones.heatGuardActive'))}</strong> ${esc(tr(pending ? 'zones.heatGuardPendingOff' : 'zones.heatGuardHint'))}</span></p>`;
}
function zonePresetLabel(preset) {
const key = `preset.${preset || 'comfort'}`;
return tr(key) === key ? (preset || 'comfort') : tr(key);
@@ -471,6 +524,7 @@ function zoneRuntimeStatusLabel(zone, effectiveMode, device = null) {
if (zone.device_manual_override === true) return tr('zones.manualDeviceControl');
if (!zone.enabled) return tr('common.disabled');
if (zone.local_thermostat_power === false) return tr('zones.localThermostatOff');
if (zone.heating_sensor_guard_active === true) return tr('zones.heatGuardActive');
if ((effectiveMode || 'off') === 'off') {
if (zone.control_source === 'automation.flow' && zone.simple_heat && zone.control_owner === 'automation') return tr(device?.power ? 'zones.flowDayPendingOff' : 'zones.flowDayOff');
return tr('zones.waiting');
@@ -494,8 +548,9 @@ function zoneCard(zone, detailed = true) {
const state = zone.enabled ? tr('common.active') : tr('common.disabled');
const sensorDetails = zone.sensor_source === 'device'
? `${tr('zones.greeTemp')}: ${fmtTemp(zone.device_temperature ?? device?.current_temperature)}`
: `${tr('zones.greeTemp')}: ${fmtTemp(zone.device_temperature)} · ${tr('zones.externalTemp')}: ${fmtTemp(zone.external_temperature)} · ${tr('zones.usedSource')}: ${zoneControlSourceLabel(zone.control_temperature_source)}`;
: `${tr('zones.greeTemp')}: ${fmtTemp(zone.device_temperature)} · ${tr('zones.externalTemp')}: ${fmtTemp(zone.external_temperature)} · ${tr('zones.usedSource')}: ${zoneControlSourceLabel(zone.control_temperature_source)}${zoneRoomSensorStatusText(zone) ? ` · ${zoneRoomSensorStatusText(zone)}` : ''}`;
const roomTemperature = zone.current_temperature ?? zone.device_temperature ?? device?.current_temperature;
const temperatureTooltip = zoneTemperatureExplanation(zone);
const manual = zone.manual_preset || 'auto';
const mode = zone.inherit_house_mode ? 'house' : zone.mode;
const effectiveMode = zone.effective_mode || (zone.inherit_house_mode ? (app.settings?.house_mode || 'off') : zone.mode) || 'off';
@@ -556,7 +611,7 @@ function zoneCard(zone, detailed = true) {
return `<article class="list-card zone-config-card ${workingDemand ? 'demanding' : ''} ${waitingDemand ? 'lockout-waiting' : ''} ${zone.enabled ? '' : 'zone-disabled'} ${deviceManualOverride ? 'manual-takeover' : ''} ${controlGroup ? 'group-controlled' : ''}" data-zone-config="${esc(zone.id)}"${groupControlStyle(visualGroup)}>
<div class="list-card-head"><div><span class="eyebrow">${esc(tr('zones.configuration'))}</span><h3>${esc(zone.name)}</h3><p>${esc(device?.name || tr('common.noDevice'))} · ${esc(tr('groups.group'))}: ${esc(groupText)}</p></div><button type="button" class="enable-toggle ${zone.enabled ? 'active' : ''}" data-action="zone-enabled" data-id="${esc(zone.id)}" data-value="${zone.enabled ? 'false' : 'true'}" aria-label="${esc(tr(zone.enabled ? 'zones.disable' : 'zones.enable'))}" title="${esc(tr('zones.automationToggleHint'))}"><span>${uiIcon(zone.enabled ? 'check' : 'circle')}</span>${esc(state)}</button></div>
<div class="zone-config-status">
<div class="zone-config-temperature"><small>${esc(tr('zones.currentStatus'))}</small><div><span>${fmtTemp(roomTemperature)}</span><b>→</b><strong>${Number.isFinite(target) ? `${target.toFixed(1)}°C` : '—'}</strong></div></div>
<div class="zone-config-temperature"><small>${esc(tr('zones.currentStatus'))}</small><div><span title="${esc(temperatureTooltip)}">${fmtTemp(roomTemperature)}</span><b>→</b><strong>${Number.isFinite(target) ? `${target.toFixed(1)}°C` : '—'}</strong></div>${zoneTemperatureHelp(zone)}</div>
<div class="zone-config-runtime"><span class="badge ${workingDemand ? 'active' : ''}">${esc(zoneRuntimeStatusLabel(zone, effectiveMode, device))}</span><span>${esc(houseModeLabel(effectiveMode))} · ${esc(zonePresetLabel(displayPreset))}</span><small>${esc(override)}</small><small><strong>${esc(tr('zones.controlOwner'))}:</strong> ${esc(zoneControlOwnerLabel(zone))}${zoneControlOwnerMeta(zone) ? ` · ${esc(zoneControlOwnerMeta(zone))}` : ''}</small></div>
</div>
<div class="zone-config-grid">
@@ -567,6 +622,7 @@ function zoneCard(zone, detailed = true) {
<div><small>${esc(tr('zones.heatAnticipation'))}</small><strong>${Number(zone.heat_anticipation_c ?? 0).toFixed(1)}°C</strong></div>
</div>
<div class="sensor-detail">${esc(sensorDetails)}</div>
${zoneHeatingGuardNotice(zone, device)}
${compressorQueuePanel(zone)}
${manualTakeover}
<div class="card-footer"><small>${esc(tr('zones.controlOnDashboard'))}</small><div class="card-menu"><button class="primary" data-action="zone-go-control" data-id="${esc(zone.id)}">${esc(tr('zones.controlNow'))}</button><button data-action="edit-zone" data-id="${esc(zone.id)}">${esc(tr('actions.edit'))}</button><details class="card-overflow"><summary aria-label="${esc(tr('actions.more'))}" title="${esc(tr('actions.more'))}">${uiIcon('more-horizontal')}</summary><div class="card-overflow-menu"><button class="danger" data-action="delete-zone" data-id="${esc(zone.id)}">${esc(tr('actions.delete'))}</button></div></details></div></div>
@@ -577,7 +633,8 @@ function zoneCard(zone, detailed = true) {
return `<article class="list-card zone-thermostat quick-control-card quick-thermostat-control ${workingDemand ? 'demanding' : ''} ${waitingDemand ? 'lockout-waiting' : ''} ${zone.enabled ? '' : 'zone-disabled'} ${deviceManualOverride ? 'manual-takeover' : ''} ${controlGroup ? 'group-controlled' : ''}" data-zone-card="${esc(zone.id)}"${groupControlStyle(visualGroup)}>
<div class="list-card-head"><div><h3>${esc(zone.name)}</h3><p>${esc(device?.name || tr('common.noDevice'))} · ${esc(zonePresetLabel(displayPreset))}</p></div><div class="zone-quick-actions">${localThermostatPowerControl}${temporaryThermostatControl}</div></div>
${disabledManualControlNotice}
<div class="thermostat-main"><div><small>${esc(tr('zones.measurement'))}</small><strong>${fmtTemp(roomTemperature)}</strong></div><div class="temperature-control compact"><button data-action="zone-temperature" data-id="${esc(zone.id)}" data-delta="-0.5">${uiIcon('minus')}</button><div class="target-temp compact editable-target" data-temperature-kind="zone" data-id="${esc(zone.id)}" data-value="${Number.isFinite(target) ? target : ''}" tabindex="0" role="button" title="${esc(tr('common.clickTemperatureToEdit'))}">${Number.isFinite(target) ? target.toFixed(1) : '--'}<small>°C</small></div><button data-action="zone-temperature" data-id="${esc(zone.id)}" data-delta="0.5">${uiIcon('plus')}</button></div><div><small>${esc(tr('zones.deviceTarget'))}</small><strong>${fmtTemp(zone.device_setpoint)}</strong></div></div>
<div class="thermostat-main"><div><small>${esc(tr('zones.measurement'))}</small><strong title="${esc(temperatureTooltip)}">${fmtTemp(roomTemperature)}</strong>${zoneTemperatureHelp(zone)}</div><div class="temperature-control compact"><button data-action="zone-temperature" data-id="${esc(zone.id)}" data-delta="-0.5">${uiIcon('minus')}</button><div class="target-temp compact editable-target" data-temperature-kind="zone" data-id="${esc(zone.id)}" data-value="${Number.isFinite(target) ? target : ''}" tabindex="0" role="button" title="${esc(tr('common.clickTemperatureToEdit'))}">${Number.isFinite(target) ? target.toFixed(1) : '--'}<small>°C</small></div><button data-action="zone-temperature" data-id="${esc(zone.id)}" data-delta="0.5">${uiIcon('plus')}</button></div><div><small>${esc(tr('zones.deviceTarget'))}</small><strong>${fmtTemp(zone.device_setpoint)}</strong></div></div>
${zoneHeatingGuardNotice(zone, device)}
<div class="preset-row quick-control-row quick-control-row-4">
${['auto', 'comfort', 'sleep', 'away'].map(preset => `<button class="${manual === preset ? 'active' : ''}" data-action="zone-preset" data-id="${esc(zone.id)}" data-value="${preset}">${esc(preset === 'sleep' ? tr('zones.sleepNow') : zonePresetLabel(preset))}</button>`).join('')}
</div>