361 files

This commit is contained in:
Home Assistant Version Control
2026-10-06 17:11:43 +00:00
parent d2c5af43e3
commit bf886c9347
362 changed files with 22060 additions and 1285 deletions
@@ -21,6 +21,8 @@ export class MaintenanceObjectDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
/** All objects — choices for the parent-object picker (2.19). */
@property({ attribute: false }) public objects: MaintenanceObjectResponse[] = [];
/** 2026-10 places: the advanced feature switch shows the place fields. */
@property({ type: Boolean }) public placesEnabled = false;
@state() private _open = false;
@state() private _loading = false;
@state() private _error = "";
@@ -39,6 +41,9 @@ export class MaintenanceObjectDialog extends LitElement {
// 2.19: attach to an existing HA device / nest under another object
@state() private _haDeviceId = "";
@state() private _parentEntryId = "";
// 2026-10 places: an HA zone ("" = home) and "remind only on site".
@state() private _place = "";
@state() private _remindOnSite = false;
@state() private _entryId: string | null = null; // null = create, string = update
// Replace (journey N1): the successor's device. The old unit's device is
// what the object links to now; a new unit usually is a new device.
@@ -63,6 +68,8 @@ export class MaintenanceObjectDialog extends LitElement {
this._notes = "";
this._haDeviceId = "";
this._parentEntryId = "";
this._place = "";
this._remindOnSite = false;
this._replacing = false;
this._error = "";
this._open = true;
@@ -96,6 +103,8 @@ export class MaintenanceObjectDialog extends LitElement {
this._notes = obj.notes || "";
this._haDeviceId = obj.ha_device_id || "";
this._parentEntryId = obj.parent_entry_id || "";
this._place = obj.place || "";
this._remindOnSite = !!obj.remind_on_site;
this._replacing = false;
this._error = "";
this._open = true;
@@ -194,6 +203,35 @@ export class MaintenanceObjectDialog extends LitElement {
`;
}
/** 2026-10 places: the zone the object is maintained at ("" = home) and
* whether its reminders wait until somebody is there. */
private _renderPlace(L: string) {
const missing = !!this._place && !this.hass?.states?.[this._place];
return html`
<ha-form
.hass=${this.hass}
.data=${{ place: this._place || undefined }}
.schema=${[{ name: "place", selector: { entity: { domain: "zone" } } }]}
.computeLabel=${() => t("place_optional", L)}
@value-changed=${(e: CustomEvent) => {
this._place = ((e.detail.value as { place?: string })?.place as string) || "";
if (!this._place) this._remindOnSite = false;
}}
></ha-form>
${missing ? html`<div class="place-missing">${t("place_missing", L)}</div>` : nothing}
<label class="place-on-site ${this._place ? "" : "disabled"}">
<input
type="checkbox"
.checked=${this._remindOnSite}
?disabled=${!this._place}
@change=${(e: Event) => (this._remindOnSite = (e.target as HTMLInputElement).checked)}
/>
<span>${t("remind_on_site", L)}</span>
</label>
${this._place ? html`<div class="place-hint">${t("remind_on_site_hint", L)}</div>` : nothing}
`;
}
private async _save(): Promise<void> {
if (this._loading) return; // synchronous re-entry guard (double-click)
if (!this._name.trim()) return;
@@ -211,6 +249,8 @@ export class MaintenanceObjectDialog extends LitElement {
notes: this._notes.trim() || null,
ha_device_id: this._haDeviceId || null,
parent_entry_id: this._parentEntryId || null,
// Only with the feature on: switched off, a stored place is kept as is.
...(this.placesEnabled ? { place: this._place || null, remind_on_site: !!this._place && this._remindOnSite } : {}),
};
const res = await runWs<{ device_swap?: DeviceSwap }>(
this,
@@ -278,6 +318,7 @@ export class MaintenanceObjectDialog extends LitElement {
@value-changed=${(e: CustomEvent) =>
(this._areaId = (e.detail.value as string) || "")}
></ha-area-picker>
${this.placesEnabled ? this._renderPlace(L) : nothing}
<ms-date-field
kind="date"
clearable
@@ -352,6 +393,25 @@ export class MaintenanceObjectDialog extends LitElement {
}
static styles = css`
.place-on-site {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
color: var(--primary-text-color);
}
.place-on-site.disabled {
color: var(--disabled-text-color, var(--secondary-text-color));
}
.place-hint,
.place-missing {
font-size: 12px;
color: var(--secondary-text-color);
margin-top: -4px;
}
.place-missing {
color: var(--warning-color, #c77700);
}
.dialog-title {
font-size: 18px;
font-weight: 500;
@@ -703,6 +703,7 @@ export class MaintenanceSettingsView extends LitElement {
{ key: "checklists", settingKey: "advanced_checklists_visible", label: t("feat_checklists", L), desc: t("feat_checklists_desc", L) },
{ key: "schedule_time", settingKey: "advanced_schedule_time_visible", label: t("feat_schedule_time", L), desc: t("feat_schedule_time_desc", L) },
{ key: "completion_actions", settingKey: "advanced_completion_actions_visible", label: t("feat_completion_actions", L), desc: t("feat_completion_actions_desc", L) },
{ key: "places", settingKey: "advanced_places_visible", label: t("feat_places", L), desc: t("feat_places_desc", L) },
];
return html`
@@ -28,6 +28,7 @@ import type { TriggerType } from "../types";
import { REQUIRED_COMPLETION_KEYS, REQUIRED_COMPLETION_LABELS } from "./required-completion-labels";
import "./ms-textfield";
import { defaultNotifyIcon } from "../helpers/notify-icons";
import { primaryTriggerEntity, triggerEntityIds } from "../helpers/trigger-entities";
import "./ms-date-field";
import "./ms-cost-input";
@@ -160,7 +161,7 @@ export function applyTypeFields(c: TriggerConfig, d: TriggerTypeFields): void {
/** Map a persisted compound condition (storage shape) to an editable draft. */
function conditionToDraft(c: TriggerConfig): CompoundConditionDraft {
const ids = c.entity_ids || (c.entity_id ? [c.entity_id] : []);
const ids = triggerEntityIds(c);
return {
entityIds: ids.join(", "),
...typeFieldsFromConfig(c),
@@ -617,8 +618,8 @@ export class MaintenanceTaskDialog extends LitElement {
// Fleet task) must still hydrate the singular field — the save path
// gates on _triggerEntityId and would otherwise NULL the whole trigger
// on an unrelated edit (issue #106).
this._triggerEntityId = tc.entity_id || (tc.entity_ids && tc.entity_ids[0]) || "";
this._triggerEntityIds = tc.entity_ids || (tc.entity_id ? [tc.entity_id] : []);
this._triggerEntityId = primaryTriggerEntity(tc) || "";
this._triggerEntityIds = triggerEntityIds(tc);
this._triggerEntityLogic = tc.entity_logic || "any";
this._setTypeFields(typeFieldsFromConfig(tc));
this._triggerCombinator = tc.trigger_combinator === "all" ? "all" : "any";
@@ -78,7 +78,7 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
@state() private _features: AdvancedFeatures = {
adaptive: false, seasonal: false, environmental: false,
budget: false, groups: false, checklists: false, schedule_time: false,
completion_actions: false,
completion_actions: false, places: false,
};
@state() private _toast = "";
private readonly _toastTimer = new ToastTimer();