113 files

This commit is contained in:
Home Assistant Version Control
2026-08-26 07:17:31 +00:00
parent 0533eaca8d
commit 15c0e35c18
113 changed files with 12448 additions and 1673 deletions
@@ -76,8 +76,11 @@ export class MaintenanceBudgetSectionCard extends LitElement {
this._busy = true;
this._error = "";
try {
const m = parseFloat(this._localMonthly);
const y = parseFloat(this._localYearly);
// An emptied field means "remove this budget" and must SEND 0 (the
// backend's off-state) — omitting the key kept the old value, so a
// budget could never be cleared from this card (bug audit 2026-08-22).
const m = this._localMonthly.trim() === "" ? 0 : parseFloat(this._localMonthly);
const y = this._localYearly.trim() === "" ? 0 : parseFloat(this._localYearly);
const settings: Record<string, number> = {};
if (!isNaN(m) && m >= 0) settings.budget_monthly = m;
if (!isNaN(y) && y >= 0) settings.budget_yearly = y;
@@ -71,7 +71,7 @@ export class MaintenanceObjectQuickActionsDialog extends LitElement {
private _onAddTask(): void {
if (!this._entryId) return;
import("../dialog-mount").then(({ openCreateTaskDialog }) => {
openCreateTaskDialog();
openCreateTaskDialog(this._entryId!);
this.close();
});
}
@@ -16,6 +16,8 @@ import { LitElement, html, css, nothing } from "lit";
import { property, state } from "lit/decorators.js";
import { sharedStyles, t, STATUS_COLORS, formatDate, formatDateTime, formatInterval, formatRecurrence, langOf } from "../styles";
import { describeWsError } from "../ws-errors";
import { isoDateLocal } from "../helpers/calendar-bucket";
import { partsForCompletion } from "../helpers/shared-parts";
import { renderWeibullSection } from "../renderers/weibull";
import { renderPredictionSection } from "../renderers/prediction";
import { renderRecommendationBars } from "../renderers/recommendation";
@@ -75,7 +77,9 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
this._showReset = false;
this._showAdaptive = false;
this._skipReason = "";
this._resetDate = new Date().toISOString().slice(0, 10);
// Local calendar date — toISOString() is UTC and prefills YESTERDAY for
// users east of UTC before their morning (bug audit 2026-08-22).
this._resetDate = isoDateLocal(new Date());
this._open = true;
await Promise.all([this._loadTask(), this._loadFeatures()]);
}
@@ -146,14 +150,36 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
private _onComplete(): void {
if (!this._entryId || !this._taskId || !this._task) return;
// Reuse the existing rich complete-dialog by mounting it on body
import("../dialog-mount").then(({ openCompleteDialog }) => {
// Reuse the existing rich complete-dialog by mounting it on body.
// Pass EVERYTHING the card's direct path passes — omitting
// required_completion_fields let a mandatory note be skipped, and a
// reading task without type+unit never rendered its value field
// (bug audit 2026-08-22).
import("../dialog-mount").then(async ({ openCompleteDialog }) => {
const task = this._task!;
const isBuy = !!(task as { part_ref?: string }).part_ref;
let parts: Parameters<typeof openCompleteDialog>[0]["parts"] = [];
if (!isBuy) {
try {
const r = await this.hass.connection.sendMessagePromise<{
objects: MaintenanceObjectResponse[];
}>({ type: "maintenance_supporter/objects", compact: true });
parts = partsForCompletion(task, this._entryId!, r.objects || [], this._lang);
} catch {
// Parts stay empty — the dialog still completes without them.
}
}
const ok = openCompleteDialog({
entry_id: this._entryId!,
task_id: this._taskId!,
task_name: this._task!.name,
checklist: this._task!.checklist || [],
adaptive_enabled: !!this._task!.adaptive_config?.enabled,
task_name: task.name,
checklist: task.checklist || [],
adaptive_enabled: !!task.adaptive_config?.enabled,
required_completion_fields: task.required_completion_fields || [],
task_type: task.type || "",
reading_unit: (task as { reading_unit?: string }).reading_unit || "",
parts,
consumes_parts: isBuy ? [] : (task.consumes_parts || []),
});
if (ok) {
this._notifyChanged("complete");
@@ -156,7 +156,14 @@ export class MaintenanceTriggerChart extends LitElement {
}
const tsMin = pts[0].ts;
const tsMax = pts[pts.length - 1].ts;
// The dashed projection extends the TIME domain. sparkline.ts builds it
// as [last sample, last sample + 30 d]; with a data-only domain its
// start sat exactly on the right plot edge and the x2 clamp below
// collapsed the line to zero width — the production degradation
// projection never rendered (found via the design-system previews,
// 2026-08-24).
const projEnd = this.projection && this.projection.length === 2 ? this.projection[1].ts : null;
const tsMax = projEnd != null ? Math.max(pts[pts.length - 1].ts, projEnd) : pts[pts.length - 1].ts;
const tsSpan = tsMax - tsMin || 1;
const withYear = needsYear(tsMin, tsMax);