257 files
This commit is contained in:
@@ -18,6 +18,7 @@ import {
|
||||
EWA_ALPHA_RANGE,
|
||||
PART_QTY_RANGE,
|
||||
SCHEDULE_OFFSET_MAX_DAYS,
|
||||
TRIGGER_DAYS_BEFORE_RANGE,
|
||||
TRIGGER_FOR_MINUTES_RANGE,
|
||||
TRIGGER_TARGET_CHANGES_RANGE,
|
||||
WARNING_DAYS_RANGE,
|
||||
@@ -39,7 +40,10 @@ const CALENDAR_KINDS = ["weekdays", "nth_weekday", "day_of_month", "calendar"];
|
||||
// #168: kinds that produce a due DATE and therefore take a time of day
|
||||
// (mirrors SCHEDULE_TIME_KINDS in config_flow_helpers.py).
|
||||
const SCHEDULE_TIME_KINDS = ["time_based", "one_time", ...CALENDAR_KINDS];
|
||||
const TRIGGER_TYPE_KEYS = ["threshold", "counter", "state_change", "runtime"];
|
||||
const TRIGGER_TYPE_KEYS = ["threshold", "counter", "state_change", "runtime", "due_date"];
|
||||
// "due_date" alone is the one-time task's date field — the trigger type
|
||||
// carries its own label.
|
||||
const triggerTypeLabel = (key: string, L: string): string => t(key === "due_date" ? "trigger_type_due_date" : key, L);
|
||||
// The type selector additionally offers "compound" (a group of conditions
|
||||
// joined by AND/OR); its per-condition sub-type is limited to the flat kinds.
|
||||
const TRIGGER_TYPE_KEYS_WITH_COMPOUND = [...TRIGGER_TYPE_KEYS, "compound"];
|
||||
@@ -68,6 +72,7 @@ interface CompoundConditionDraft {
|
||||
targetChanges: string;
|
||||
runtimeHours: string;
|
||||
onStates: string;
|
||||
daysBefore: string;
|
||||
/** Original keys this editor has no fields for (attribute, baseline, ...).
|
||||
* Spread back on save so a compound roundtrip never drops them (#103 class). */
|
||||
carry: Partial<TriggerConfig>;
|
||||
@@ -78,7 +83,7 @@ function emptyCondition(): CompoundConditionDraft {
|
||||
entityIds: "", type: "threshold", attribute: "", above: "", below: "",
|
||||
equals: "", notEquals: "", forMinutes: "0",
|
||||
targetValue: "", deltaMode: false, fromState: "", toState: "",
|
||||
targetChanges: "", runtimeHours: "", onStates: "", carry: {},
|
||||
targetChanges: "", runtimeHours: "", onStates: "", daysBefore: "0", carry: {},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -90,6 +95,7 @@ const MANAGED_CONDITION_KEYS = new Set([
|
||||
"trigger_target_value", "trigger_delta_mode",
|
||||
"trigger_from_state", "trigger_to_state", "trigger_target_changes",
|
||||
"trigger_runtime_hours", "trigger_on_states",
|
||||
"trigger_days_before",
|
||||
]);
|
||||
|
||||
/** The per-type trigger fields BOTH editors own — the flat trigger form and
|
||||
@@ -117,6 +123,7 @@ export function typeFieldsFromConfig(c: TriggerConfig): TriggerTypeFields {
|
||||
targetChanges: c.trigger_target_changes?.toString() ?? "",
|
||||
runtimeHours: c.trigger_runtime_hours?.toString() ?? "",
|
||||
onStates: (c.trigger_on_states || []).join(", "),
|
||||
daysBefore: c.trigger_days_before?.toString() ?? "0",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -144,6 +151,9 @@ export function applyTypeFields(c: TriggerConfig, d: TriggerTypeFields): void {
|
||||
const h = parseFloat(d.runtimeHours); if (!isNaN(h)) c.trigger_runtime_hours = h;
|
||||
const on = (d.onStates || "").split(",").map((s) => s.trim()).filter(Boolean);
|
||||
if (on.length > 0) c.trigger_on_states = on;
|
||||
} else if (d.type === "due_date") {
|
||||
const days = parseInt(d.daysBefore, 10);
|
||||
c.trigger_days_before = isNaN(days) ? 0 : days;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,6 +194,13 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
@property({ type: Boolean, attribute: "checklists-enabled" }) public checklistsEnabled = false;
|
||||
@property({ type: Boolean, attribute: "schedule-time-enabled" }) public scheduleTimeEnabled = false;
|
||||
@property({ type: Boolean, attribute: "completion-actions-enabled" }) public completionActionsEnabled = false;
|
||||
/** Advanced features (Settings → Advanced Features): the dialog offers what
|
||||
* the panel then shows — with Adaptive off it let you switch adaptive
|
||||
* scheduling on while the feedback question and every result stayed
|
||||
* hidden (audit 2026-09-28). What a task already has stays visible. */
|
||||
@property({ type: Boolean, attribute: "adaptive-feature" }) public adaptiveFeature = false;
|
||||
@property({ type: Boolean, attribute: "seasonal-feature" }) public seasonalFeature = false;
|
||||
@property({ type: Boolean, attribute: "environmental-feature" }) public environmentalFeature = false;
|
||||
@property({ type: Number, attribute: "default-warning-days" }) public defaultWarningDays = 7;
|
||||
/** The object's spare parts — offered as "consumes parts" checkboxes. */
|
||||
@state() private parts: Array<{ id: string; name: string; unit?: string }> = [];
|
||||
@@ -285,6 +302,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
@state() private _triggerToState = "";
|
||||
@state() private _triggerTargetChanges = "";
|
||||
@state() private _triggerRuntimeHours = "";
|
||||
@state() private _triggerDaysBefore = "0";
|
||||
@state() private _triggerRuntimeMaxSession = "";
|
||||
// Comma-separated "running" states for the runtime trigger (#103) —
|
||||
// empty = the backend default ["on"]. Must roundtrip on edit: adopted
|
||||
@@ -366,6 +384,13 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
// so ha-form can drive the data fields when the service schema is known.
|
||||
@state() private _actionService = "";
|
||||
@state() private _actionTargetEntity = "";
|
||||
/** The stored target as it came, and the entity the field started with.
|
||||
* The field edits one entity, but the API and imports accept several
|
||||
* entities, devices, areas, labels and floors — rebuilding the target
|
||||
* from the field dropped them on every save, a rename included (audit
|
||||
* 2026-09-28). An untouched field sends the stored target back verbatim. */
|
||||
private _actionTargetStored: Record<string, unknown> | null = null;
|
||||
private _actionTargetInitial = "";
|
||||
/** 2.95: not when the task completes itself (a reset wired by discovery). */
|
||||
@state() private _actionSkipAuto = false;
|
||||
/** 2.95: the task already has an action (suggested setups wire counter
|
||||
@@ -382,7 +407,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
@state() private _qcNotes = "";
|
||||
@state() private _qcCost = "";
|
||||
@state() private _qcDuration = "";
|
||||
@state() private _qcFeedback: "" | "needed" | "not_needed" = "";
|
||||
@state() private _qcFeedback: "" | "needed" | "not_needed" | "not_sure" = "";
|
||||
|
||||
// Environmental entity (adaptive_config)
|
||||
@state() private _environmentalEntity = "";
|
||||
@@ -398,6 +423,9 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
@state() private _adaptiveSeasonal = true;
|
||||
@state() private _adaptivePrediction = true;
|
||||
private _adaptiveInitial = "";
|
||||
/** The task had adaptive scheduling on when the dialog opened — its
|
||||
* section stays reachable even with the Adaptive feature off. */
|
||||
private _adaptiveWasEnabled = false;
|
||||
|
||||
private _adaptiveSnapshot(): string {
|
||||
return JSON.stringify([
|
||||
@@ -544,6 +572,10 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
this._actionService = oca.service;
|
||||
const tgt = oca.target?.entity_id;
|
||||
this._actionTargetEntity = Array.isArray(tgt) ? (tgt[0] || "") : (tgt || "");
|
||||
this._actionTargetStored = oca.target && typeof oca.target === "object"
|
||||
? JSON.parse(JSON.stringify(oca.target)) as Record<string, unknown>
|
||||
: null;
|
||||
this._actionTargetInitial = this._actionTargetEntity;
|
||||
this._actionData = (oca.data && typeof oca.data === "object") ? { ...oca.data } : {};
|
||||
this._actionDataJsonFallback = "";
|
||||
this._actionSkipAuto = oca.skip_auto === true;
|
||||
@@ -551,6 +583,8 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
} else {
|
||||
this._actionService = "";
|
||||
this._actionTargetEntity = "";
|
||||
this._actionTargetStored = null;
|
||||
this._actionTargetInitial = "";
|
||||
this._actionData = {};
|
||||
this._actionDataJsonFallback = "";
|
||||
this._actionSkipAuto = false;
|
||||
@@ -560,7 +594,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
this._qcNotes = qcd?.notes || "";
|
||||
this._qcCost = qcd?.cost != null ? String(qcd.cost) : "";
|
||||
this._qcDuration = qcd?.duration != null ? String(qcd.duration) : "";
|
||||
this._qcFeedback = (qcd?.feedback as "needed" | "not_needed" | undefined) || "";
|
||||
this._qcFeedback = (qcd?.feedback as "needed" | "not_needed" | "not_sure" | undefined) || "";
|
||||
|
||||
const ac: Partial<AdaptiveConfig> = task.adaptive_config || {};
|
||||
this._environmentalEntity = ac.environmental_entity || "";
|
||||
@@ -574,6 +608,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
this._adaptiveSeasonal = ac.seasonal_enabled !== false;
|
||||
this._adaptivePrediction = ac.sensor_prediction_enabled !== false;
|
||||
this._adaptiveInitial = this._adaptiveSnapshot();
|
||||
this._adaptiveWasEnabled = this._adaptiveEnabled;
|
||||
|
||||
if (task.trigger_config) {
|
||||
const tc = task.trigger_config;
|
||||
@@ -668,9 +703,12 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
this._adaptiveSeasonal = true;
|
||||
this._adaptivePrediction = true;
|
||||
this._adaptiveInitial = this._adaptiveSnapshot();
|
||||
this._adaptiveWasEnabled = false;
|
||||
// v1.3.0
|
||||
this._actionService = "";
|
||||
this._actionTargetEntity = "";
|
||||
this._actionTargetStored = null;
|
||||
this._actionTargetInitial = "";
|
||||
this._actionData = {};
|
||||
this._actionDataJsonFallback = "";
|
||||
this._actionSkipAuto = false;
|
||||
@@ -703,6 +741,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
targetChanges: this._triggerTargetChanges,
|
||||
runtimeHours: this._triggerRuntimeHours,
|
||||
onStates: this._triggerOnStates,
|
||||
daysBefore: this._triggerDaysBefore,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -721,6 +760,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
this._triggerTargetChanges = f.targetChanges;
|
||||
this._triggerRuntimeHours = f.runtimeHours;
|
||||
this._triggerOnStates = f.onStates;
|
||||
this._triggerDaysBefore = f.daysBefore;
|
||||
}
|
||||
|
||||
private _resetTriggerFields(): void {
|
||||
@@ -786,7 +826,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
const svc = this._actionService.trim();
|
||||
if (!svc || !/^[a-z][a-z0-9_]*\.[a-z0-9_]+$/.test(svc)) {
|
||||
this._actionTestResult = "error";
|
||||
this._actionTestError = "Invalid service format (expected 'domain.service')";
|
||||
this._actionTestError = t("action_test_bad_format", this._lang);
|
||||
setTimeout(() => { this._actionTestResult = ""; this._actionTestError = ""; }, 5000);
|
||||
return;
|
||||
}
|
||||
@@ -795,7 +835,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
// 2. Service exists?
|
||||
if (!this.hass?.services?.[domain]?.[name]) {
|
||||
this._actionTestResult = "error";
|
||||
this._actionTestError = `Service "${svc}" is not registered in Home Assistant. Check spelling and that the integration providing it is loaded.`;
|
||||
this._actionTestError = t("action_test_unknown_service", this._lang).replace("{service}", svc);
|
||||
setTimeout(() => { this._actionTestResult = ""; this._actionTestError = ""; }, 8000);
|
||||
return;
|
||||
}
|
||||
@@ -813,14 +853,19 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
]);
|
||||
if (entityDomain !== domain && !crossDomainServices.has(domain)) {
|
||||
this._actionTestResult = "error";
|
||||
this._actionTestError = `Service "${svc}" only works on ${domain}.* entities; entity "${tgt}" is in ${entityDomain}.* — pick a service that matches the entity domain (e.g. ${entityDomain}.${name})`;
|
||||
this._actionTestError = t("action_test_domain_mismatch", this._lang)
|
||||
.replace("{service}", svc)
|
||||
.replace("{domain}", domain)
|
||||
.replace("{entity}", tgt)
|
||||
.replace("{entity_domain}", entityDomain)
|
||||
.replace("{suggestion}", `${entityDomain}.${name}`);
|
||||
setTimeout(() => { this._actionTestResult = ""; this._actionTestError = ""; }, 8000);
|
||||
return;
|
||||
}
|
||||
// 5. Entity exists?
|
||||
if (!this.hass.states?.[tgt]) {
|
||||
this._actionTestResult = "error";
|
||||
this._actionTestError = `Target entity "${tgt}" not found in Home Assistant — the entity may have been renamed or its integration removed.`;
|
||||
this._actionTestError = t("action_test_entity_missing", this._lang).replace("{entity}", tgt);
|
||||
setTimeout(() => { this._actionTestResult = ""; this._actionTestError = ""; }, 8000);
|
||||
return;
|
||||
}
|
||||
@@ -868,6 +913,34 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
}));
|
||||
}
|
||||
|
||||
/** The stored target reaches beyond the one entity the field shows. */
|
||||
private _actionTargetExtended(): boolean {
|
||||
const st = this._actionTargetStored;
|
||||
if (!st) return false;
|
||||
return Object.entries(st).some(([key, v]) =>
|
||||
key === "entity_id" ? Array.isArray(v) && v.length > 1 : Array.isArray(v) ? v.length > 0 : !!v);
|
||||
}
|
||||
|
||||
/** Names of everything the stored target points at, for the hint. */
|
||||
private _actionTargetSummary(): string {
|
||||
const st = this._actionTargetStored || {};
|
||||
const ids = (v: unknown): string[] =>
|
||||
(Array.isArray(v) ? v : [v]).filter((s): s is string => typeof s === "string" && !!s);
|
||||
const h = this.hass as unknown as {
|
||||
states?: Record<string, { attributes?: { friendly_name?: string } }>;
|
||||
devices?: Record<string, { name_by_user?: string | null; name?: string | null }>;
|
||||
areas?: Record<string, { name?: string }>;
|
||||
floors?: Record<string, { name?: string }>;
|
||||
};
|
||||
return [
|
||||
...ids(st.entity_id).map((id) => h.states?.[id]?.attributes?.friendly_name || id),
|
||||
...ids(st.device_id).map((id) => h.devices?.[id]?.name_by_user || h.devices?.[id]?.name || id),
|
||||
...ids(st.area_id).map((id) => h.areas?.[id]?.name || id),
|
||||
...ids(st.floor_id).map((id) => h.floors?.[id]?.name || id),
|
||||
...ids(st.label_id),
|
||||
].join(", ");
|
||||
}
|
||||
|
||||
private _renderCompletionActionsSection(L: string) {
|
||||
if (!this.completionActionsEnabled && !this._actionPresent) return nothing;
|
||||
const schema = this._serviceSchema();
|
||||
@@ -903,6 +976,9 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
this._actionTargetEntity = v.target_entity || "";
|
||||
}}
|
||||
></ha-form>
|
||||
${this._actionTargetExtended() && this._actionTargetEntity.trim() === this._actionTargetInitial
|
||||
? html`<p class="field-help ca-target-more">${t("on_complete_action_target_more", L).replace("{targets}", this._actionTargetSummary())}</p>`
|
||||
: nothing}
|
||||
<p class="field-help ca-domain-hint">
|
||||
${t("on_complete_action_target_hint", L)}
|
||||
</p>
|
||||
@@ -972,10 +1048,11 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
></ms-textfield>
|
||||
<select class="qc-feedback"
|
||||
.value=${this._qcFeedback}
|
||||
@change=${(e: Event) => { this._qcFeedback = (e.target as HTMLSelectElement).value as "" | "needed" | "not_needed"; }}>
|
||||
@change=${(e: Event) => { this._qcFeedback = (e.target as HTMLSelectElement).value as "" | "needed" | "not_needed" | "not_sure"; }}>
|
||||
<option value="">${t("quick_complete_defaults_feedback_none", L)}</option>
|
||||
<option value="needed">${t("quick_complete_defaults_feedback_needed", L)}</option>
|
||||
<option value="not_needed">${t("quick_complete_defaults_feedback_not_needed", L)}</option>
|
||||
<option value="not_sure">${t("feedback_not_sure", L)}</option>
|
||||
</select>
|
||||
</details>` : nothing}
|
||||
`;
|
||||
@@ -1233,9 +1310,9 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
.value=${s.unit || ""}
|
||||
@input=${(e: Event) => this._patchReading(s.id, { unit: (e.target as HTMLInputElement).value })}
|
||||
></ms-textfield>
|
||||
<mwc-icon-button class="phase-remove reading-remove" @click=${() => (this._readings = this._readings.filter((x) => x.id !== s.id))}>
|
||||
<ha-icon-button class="phase-remove reading-remove" @click=${() => (this._readings = this._readings.filter((x) => x.id !== s.id))}>
|
||||
<ha-icon icon="mdi:delete-outline"></ha-icon>
|
||||
</mwc-icon-button>
|
||||
</ha-icon-button>
|
||||
</div>
|
||||
`)}
|
||||
${this._readings.length < MAX_READING_SLOTS ? html`
|
||||
@@ -1307,9 +1384,9 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
@input=${(e: Event) => this._patchPhaseDef(d.id, { partQty: (e.target as HTMLInputElement).value })} />
|
||||
` : nothing}
|
||||
` : nothing}
|
||||
<mwc-icon-button class="phase-remove" @click=${() => this._removePhaseDef(d.id)}>
|
||||
<ha-icon-button class="phase-remove" @click=${() => this._removePhaseDef(d.id)}>
|
||||
<ha-icon icon="mdi:delete-outline"></ha-icon>
|
||||
</mwc-icon-button>
|
||||
</ha-icon-button>
|
||||
</div>
|
||||
${this.checklistsEnabled ? html`
|
||||
<textarea
|
||||
@@ -1692,7 +1769,11 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
if (svc && /^[a-z][a-z0-9_]*\.[a-z0-9_]+$/.test(svc)) {
|
||||
const action: Record<string, unknown> = { service: svc };
|
||||
const tgt = this._actionTargetEntity.trim();
|
||||
if (tgt) action.target = { entity_id: tgt };
|
||||
if (this._actionTargetStored && tgt === this._actionTargetInitial) {
|
||||
action.target = JSON.parse(JSON.stringify(this._actionTargetStored));
|
||||
} else if (tgt) {
|
||||
action.target = { entity_id: tgt };
|
||||
}
|
||||
const dataDict = this._buildActionData();
|
||||
if (Object.keys(dataDict).length > 0) {
|
||||
action.data = dataDict;
|
||||
@@ -1741,7 +1822,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
@change=${(e: Event) => (this._triggerType = (e.target as HTMLSelectElement).value as TriggerType)}
|
||||
>
|
||||
${TRIGGER_TYPE_KEYS_WITH_COMPOUND.map(
|
||||
(key) => html`<option value=${key} ?selected=${key === this._triggerType}>${t(key, L)}</option>`
|
||||
(key) => html`<option value=${key} ?selected=${key === this._triggerType}>${triggerTypeLabel(key, L)}</option>`
|
||||
)}
|
||||
</select>
|
||||
</div>
|
||||
@@ -1917,7 +1998,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
@change=${(e: Event) => this._patchCondition(i, { type: (e.target as HTMLSelectElement).value as TriggerType })}
|
||||
>
|
||||
${TRIGGER_TYPE_KEYS.map(
|
||||
(key) => html`<option value=${key} ?selected=${key === c.type}>${t(key, L)}</option>`
|
||||
(key) => html`<option value=${key} ?selected=${key === c.type}>${triggerTypeLabel(key, L)}</option>`
|
||||
)}
|
||||
</select>
|
||||
</div>
|
||||
@@ -1990,6 +2071,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
* adapt. Collapsed unless adaptive is already enabled. */
|
||||
private _renderAdaptiveSection(L: string) {
|
||||
if (this._scheduleType === "one_time" || this._scheduleType === "manual") return nothing;
|
||||
if (!this.adaptiveFeature && !this._adaptiveWasEnabled) return nothing;
|
||||
return html`
|
||||
<details class="adaptive-section" ?open=${this._adaptiveEnabled}>
|
||||
<summary>${t("adaptive_section_title", L)}</summary>
|
||||
@@ -2025,14 +2107,14 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
.value=${this._adaptiveAlpha}
|
||||
@input=${(e: Event) => (this._adaptiveAlpha = (e.target as HTMLInputElement).value)}
|
||||
></ms-textfield>
|
||||
<label>
|
||||
${this.seasonalFeature ? html`<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
.checked=${this._adaptiveSeasonal}
|
||||
@change=${(e: Event) => (this._adaptiveSeasonal = (e.target as HTMLInputElement).checked)}
|
||||
/>
|
||||
${t("adaptive_seasonal_enabled", L)}
|
||||
</label>
|
||||
</label>` : nothing}
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -2177,6 +2259,13 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
})}
|
||||
`;
|
||||
}
|
||||
if (c.type === "due_date") {
|
||||
return html`
|
||||
<ms-textfield label="${t("days_before", L)}" type="number" step="1"
|
||||
min=${TRIGGER_DAYS_BEFORE_RANGE[0]} max=${TRIGGER_DAYS_BEFORE_RANGE[1]} .value=${c.daysBefore}
|
||||
@input=${(e: Event) => this._patchCondition(i, { daysBefore: (e.target as HTMLInputElement).value })}></ms-textfield>
|
||||
`;
|
||||
}
|
||||
return nothing;
|
||||
}
|
||||
|
||||
@@ -2641,6 +2730,17 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
).replace("{count}", String(n)),
|
||||
);
|
||||
parts.push(t("trigger_hint_state_now", L).replace("{value}", String(st.state)));
|
||||
} else if (this._triggerType === "due_date") {
|
||||
const days = parseInt(this._triggerDaysBefore, 10) || 0;
|
||||
parts.push(
|
||||
days === 0 ? t("trigger_hint_due_date_same_day", L)
|
||||
: days === 1 ? t("trigger_hint_due_date_one", L)
|
||||
: t("trigger_hint_due_date", L).replace("{days}", String(days)),
|
||||
);
|
||||
const reported = raw == null ? "" : String(raw);
|
||||
if (reported && !isNaN(Date.parse(reported))) {
|
||||
parts.push(t("trigger_hint_due_date_now", L).replace("{date}", formatDate(reported, L)));
|
||||
}
|
||||
}
|
||||
if (!parts.length) return overlap;
|
||||
return html`<div class="trigger-live-hint">${parts.join(" ")}</div>${overlap}`;
|
||||
@@ -2788,6 +2888,20 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
<div class="field-help">${t("runtime_on_states_help", L)}</div>
|
||||
`;
|
||||
}
|
||||
if (this._triggerType === "due_date") {
|
||||
return html`
|
||||
<ms-textfield
|
||||
label="${t("days_before", L)}"
|
||||
type="number"
|
||||
step="1"
|
||||
min=${TRIGGER_DAYS_BEFORE_RANGE[0]}
|
||||
max=${TRIGGER_DAYS_BEFORE_RANGE[1]}
|
||||
.value=${this._triggerDaysBefore}
|
||||
@input=${(e: Event) => (this._triggerDaysBefore = (e.target as HTMLInputElement).value)}
|
||||
></ms-textfield>
|
||||
<div class="field-help">${t("days_before_help", L)}</div>
|
||||
`;
|
||||
}
|
||||
return nothing;
|
||||
}
|
||||
|
||||
@@ -3086,7 +3200,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
</div>` : nothing}
|
||||
` : nothing}
|
||||
${this._renderTriggerFields()}
|
||||
${this._scheduleType === "sensor_based" ? html`
|
||||
${this._scheduleType === "sensor_based" && (this.environmentalFeature || this._environmentalInitial) ? html`
|
||||
${this._entityPickerFallback ? html`
|
||||
<ms-textfield
|
||||
label="${t("environmental_entity_optional", L)}"
|
||||
@@ -3389,6 +3503,8 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
font-size: 13px;
|
||||
}
|
||||
.phase-remove {
|
||||
display: inline-flex;
|
||||
--ha-icon-button-size: 36px;
|
||||
--mdc-icon-button-size: 36px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user