This commit is contained in:
Mateusz Gruszczyński
2026-09-15 09:04:29 +02:00
parent 0a2fb8c6c7
commit 2ff310bdc5
37 changed files with 731 additions and 141 deletions
+49 -3
View File
@@ -1,3 +1,31 @@
function outdoorHistorySeries(devices, rows) {
const selectedIds = new Set((devices || []).map(device => device.id));
const handled = new Set();
const definitions = [];
(app.deviceGroups || []).forEach(group => {
if (!group.outdoor_temperature_device_id) return;
const members = (group.device_ids || []).filter(id => selectedIds.has(id));
if (!members.length) return;
const representative = members.includes(group.outdoor_temperature_device_id) ? group.outdoor_temperature_device_id : members[0];
if (!rows.some(row => row.device_id === representative && Number.isFinite(historyNumber(row.outdoor_temperature)))) return;
members.forEach(id => handled.add(id));
definitions.push({ representative, label: `${group.name} · ${tr('history.sharedOutdoor')}` });
});
(devices || []).filter(device => !handled.has(device.id)).forEach(device => {
if (rows.some(row => row.device_id === device.id && Number.isFinite(historyNumber(row.outdoor_temperature)))) {
definitions.push({ representative: device.id, label: device.name });
}
});
return definitions.map((item, index) => ({
label: item.label,
color: historySeriesColor(index),
value: row => row.device_id === item.representative ? historyNumber(row.outdoor_temperature) : NaN,
}));
}
function renderHistorySummary() {
const host = $('#historySummary'); if (!host) return;
if (app.historyTab === 'energy') { renderEnergyHistorySummary(); return; }
@@ -20,7 +48,7 @@ function renderOverviewHistory() {
const indoorSeries = app.devices.map((device, index) => ({ label: device.name, color: historySeriesColor(index), value: row => !row.zone_id && !row.entity_id && row.device_id === device.id ? historyNumber(row.indoor_temperature) : NaN }));
drawLineChart($('#overviewIndoorChart'), indoorSeries, deviceRows, { height: 360 }); renderLegend($('#overviewIndoorChartLegend'), indoorSeries);
const outdoorDeviceSeries = app.devices.filter(device => deviceRows.some(row => row.device_id === device.id && Number.isFinite(historyNumber(row.outdoor_temperature)))).map((device, index) => ({ label: `${device.name} · ${tr('history.greeOutdoor')}`, color: historySeriesColor(index), value: row => !row.zone_id && !row.entity_id && row.device_id === device.id ? historyNumber(row.outdoor_temperature) : NaN }));
const outdoorDeviceSeries = outdoorHistorySeries(app.devices, deviceRows).map(item => ({ ...item, label: item.label.includes(' · ') ? item.label : `${item.label} · ${tr('history.greeOutdoor')}` }));
const outdoorEntities = [...new Set(app.historyData.sensors.filter(row => row.kind === 'outdoor').map(row => row.entity_id))];
const outdoorHaSeries = outdoorEntities.map((entity, index) => ({ label: `HA · ${haSensorLabel(entity)}`, color: historySeriesColor(index + outdoorDeviceSeries.length), dash: [6, 4], value: row => row.entity_id === entity ? historyNumber(row.temperature) : NaN }));
const outdoorRows = [...deviceRows, ...app.historyData.sensors.filter(row => row.kind === 'outdoor')];
@@ -68,7 +96,7 @@ function renderDeviceHistory() {
host.innerHTML = historyChartMarkup('deviceIndoorChart', tr('history.deviceIndoor'), tr('history.deviceIndoorHint')) + historyChartMarkup('deviceOutdoorChart', tr('history.deviceOutdoor'), tr('history.deviceOutdoorHint')) + historyChartMarkup('deviceTargetChart', tr('history.deviceTargets'), tr('history.deviceTargetsHint'), true);
const devices = selected === 'all' ? app.devices : app.devices.filter(device => device.id === selected);
const indoor = devices.map((device, index) => ({ label: device.name, color: historySeriesColor(index), value: row => row.device_id === device.id ? historyNumber(row.indoor_temperature) : NaN }));
const outdoor = devices.filter(device => rows.some(row => row.device_id === device.id && Number.isFinite(historyNumber(row.outdoor_temperature)))).map((device, index) => ({ label: device.name, color: historySeriesColor(index), value: row => row.device_id === device.id ? historyNumber(row.outdoor_temperature) : NaN }));
const outdoor = outdoorHistorySeries(devices, rows);
const targets = devices.map((device, index) => ({ label: device.name, color: historySeriesColor(index), dash: [6, 4], value: row => row.device_id === device.id ? historyNumber(row.target_temperature) : NaN }));
drawLineChart($('#deviceIndoorChart'), indoor, rows, { height: 350 }); renderLegend($('#deviceIndoorChartLegend'), indoor);
drawLineChart($('#deviceOutdoorChart'), outdoor, rows, { height: 310 }); renderLegend($('#deviceOutdoorChartLegend'), outdoor);
@@ -87,9 +115,19 @@ function renderSensorHistory() {
function customSeriesOptions() {
const items = [];
const groupedOutdoorIds = new Set();
(app.deviceGroups || []).forEach(group => {
if (!group.outdoor_temperature_device_id) return;
const representative = (group.device_ids || []).includes(group.outdoor_temperature_device_id)
? group.outdoor_temperature_device_id
: (group.device_ids || [])[0];
if (!representative) return;
(group.device_ids || []).forEach(id => groupedOutdoorIds.add(id));
items.push([`installation|${group.id}|outdoor`, `${group.name} · ${tr('history.sharedOutdoor')}`]);
});
app.devices.forEach(device => {
items.push([`device|${device.id}|indoor`, `${device.name} · ${tr('history.indoorTemperature')}`]);
items.push([`device|${device.id}|outdoor`, `${device.name} · ${tr('history.greeOutdoor')}`]);
if (!groupedOutdoorIds.has(device.id)) items.push([`device|${device.id}|outdoor`, `${device.name} · ${tr('history.greeOutdoor')}`]);
items.push([`device|${device.id}|target`, `${device.name} · ${tr('history.deviceTarget')}`]);
});
app.zones.forEach(zone => {
@@ -113,6 +151,14 @@ function customSeriesDefinition(key, index = 0) {
const fields = { indoor: 'indoor_temperature', outdoor: 'outdoor_temperature', target: 'target_temperature' };
return { key, label: `${device.name} · ${labels[field] || field}`, color, dash: field === 'target' ? [6, 4] : [], value: row => !row.zone_id && !row.entity_id && row.device_id === id ? historyNumber(row[fields[field]]) : NaN };
}
if (kind === 'installation' && field === 'outdoor') {
const group = (app.deviceGroups || []).find(item => item.id === id); if (!group) return null;
const representative = (group.device_ids || []).includes(group.outdoor_temperature_device_id)
? group.outdoor_temperature_device_id
: (group.device_ids || [])[0];
if (!representative) return null;
return { key, label: `${group.name} · ${tr('history.sharedOutdoor')}`, color, value: row => !row.zone_id && !row.entity_id && row.device_id === representative ? historyNumber(row.outdoor_temperature) : NaN };
}
if (kind === 'zone') {
const zone = app.zones.find(item => item.id === id); if (!zone) return null;
const fields = { control: ['control_temperature', tr('history.controlTemperature')], gree: ['gree_temperature', tr('history.greeSensor')], external: ['external_temperature', tr('history.roomSensor')], target: ['target_temperature', tr('history.comfortTarget')], device_target: ['device_setpoint', tr('history.deviceSetpoint')], outdoor: ['outdoor_temperature', tr('history.outdoorTemperature')] };