429 files
This commit is contained in:
1 parent
472c19bceb
commit
963cce81a7
566 files changed
+50311
-47616
No files matched your search
@@ -244,6 +244,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
// NFC
|
||||
@state() private _lastPerformed = "";
|
||||
@state() private _nfcTagId = "";
|
||||
@state() private _requireTagScan = false;
|
||||
// v2.20 (#83): unit for `reading`-type tasks ("kWh", "m³", ...)
|
||||
@state() private _readingUnit = "";
|
||||
/** The picked links, keyed by `partLinkKey` — the (entry_id, part_id) pair,
|
||||
@@ -263,12 +264,16 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
// Task phases (#139): editable defs + the cycle order. The v1 editor keeps
|
||||
// each phase to name + checklist + at most ONE own-object part (the mower
|
||||
// "14 new blades" case); richer per-phase config exists at the WS level.
|
||||
// `carry` holds def fields the editor has no form for (notes,
|
||||
// required_completion_fields, future keys) and `extraParts` any links
|
||||
// beyond the first — both re-merged on save so a no-op edit never wipes
|
||||
// them (#106 pattern; same passthrough idea as trigger_config's carry).
|
||||
// `carry` holds def fields the editor has no form for (notes, future
|
||||
// keys) and `extraParts` any links beyond the first — both re-merged on
|
||||
// save so a no-op edit never wipes them (#106 pattern; same passthrough
|
||||
// idea as trigger_config's carry). Required completion fields are a real
|
||||
// form now (#139 follow-up): `reqOverride` distinguishes "inherit the
|
||||
// task's list" (off) from an explicit per-phase list — including the
|
||||
// meaningful EMPTY override "this phase demands nothing".
|
||||
@state() private _phaseDefs: Array<{
|
||||
id: string; name: string; checklistText: string; partId: string; partQty: string;
|
||||
reqOverride: boolean; reqFields: string[];
|
||||
extraParts: TaskPartLink[]; carry: Record<string, unknown>;
|
||||
}> = [];
|
||||
@state() private _phaseSeq: string[] = [];
|
||||
@@ -348,6 +353,11 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
this._entryId = entryId;
|
||||
this._taskId = task.id;
|
||||
this._error = "";
|
||||
// The object picker belongs to CREATE only. openCreate("", objects) left
|
||||
// the choices behind, so Create → Cancel → Edit showed the dropdown in
|
||||
// edit mode — and picking another object re-pointed _entryId while
|
||||
// _taskId kept the old task (audit 2026-08-29).
|
||||
this._objectChoices = [];
|
||||
this._name = task.name;
|
||||
this._type = task.type;
|
||||
this._scheduleType = task.schedule_type;
|
||||
@@ -389,6 +399,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
this._enabled = task.enabled !== false;
|
||||
this._lastPerformed = task.last_performed || "";
|
||||
this._nfcTagId = task.nfc_tag_id || "";
|
||||
this._requireTagScan = !!task.require_tag_scan;
|
||||
this._readingUnit = task.reading_unit || "";
|
||||
// Whole link, entry_id included — hydrating only part_id would turn every
|
||||
// shared-pool link into an own-part link on the next save (#111).
|
||||
@@ -401,14 +412,28 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
|
||||
this._checklistText = (task.checklist || []).join("\n");
|
||||
this._phaseDefs = Object.entries(task.phases || {}).map(([id, def]) => {
|
||||
const { name: _n, checklist: _c, consumes_parts: _p, ...carry } = def as unknown as Record<string, unknown>;
|
||||
const {
|
||||
name: _n, checklist: _c, consumes_parts: _p, required_completion_fields: _r,
|
||||
...carry
|
||||
} = def as unknown as Record<string, unknown>;
|
||||
// The editor's single part field is the first OWN link (no entry_id —
|
||||
// the picker only lists the object's own parts). Every other link,
|
||||
// shared pools (#111) first among them, rides along whole in
|
||||
// `extraParts` and is re-emitted on save: hydrating index 0 blindly
|
||||
// turned a phase whose first link is foreign into an own-part link
|
||||
// (or lost it) on the next save (audit 2026-08-29).
|
||||
const links = def.consumes_parts || [];
|
||||
const ownIdx = links.findIndex((l) => !l.entry_id);
|
||||
const own = ownIdx >= 0 ? links[ownIdx] : undefined;
|
||||
return {
|
||||
id,
|
||||
name: def.name || id,
|
||||
checklistText: (def.checklist || []).join("\n"),
|
||||
partId: def.consumes_parts?.[0]?.part_id || "",
|
||||
partQty: def.consumes_parts?.[0]?.quantity != null ? String(def.consumes_parts[0].quantity) : "",
|
||||
extraParts: (def.consumes_parts || []).slice(1),
|
||||
partId: own?.part_id || "",
|
||||
partQty: own?.quantity != null ? String(own.quantity) : "",
|
||||
reqOverride: def.required_completion_fields !== undefined,
|
||||
reqFields: [...(def.required_completion_fields || [])],
|
||||
extraParts: links.filter((_, i) => i !== ownIdx).map((l) => ({ ...l })),
|
||||
carry,
|
||||
};
|
||||
});
|
||||
@@ -525,6 +550,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
this._enabled = true;
|
||||
this._lastPerformed = "";
|
||||
this._nfcTagId = "";
|
||||
this._requireTagScan = false;
|
||||
this._readingUnit = "";
|
||||
this._consumesParts = {};
|
||||
this._responsibleUserId = null;
|
||||
@@ -1032,7 +1058,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
|
||||
private _addPhaseDef(): void {
|
||||
const id = this._phaseSlug(`phase-${this._phaseDefs.length + 1}`);
|
||||
this._phaseDefs = [...this._phaseDefs, { id, name: "", checklistText: "", partId: "", partQty: "", extraParts: [], carry: {} }];
|
||||
this._phaseDefs = [...this._phaseDefs, { id, name: "", checklistText: "", partId: "", partQty: "", reqOverride: false, reqFields: [], extraParts: [], carry: {} }];
|
||||
}
|
||||
|
||||
private _removePhaseDef(id: string): void {
|
||||
@@ -1040,7 +1066,10 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
this._phaseSeq = this._phaseSeq.filter((s) => s !== id);
|
||||
}
|
||||
|
||||
private _patchPhaseDef(id: string, patch: Partial<{ name: string; checklistText: string; partId: string; partQty: string }>): void {
|
||||
private _patchPhaseDef(
|
||||
id: string,
|
||||
patch: Partial<{ name: string; checklistText: string; partId: string; partQty: string; reqOverride: boolean; reqFields: string[] }>,
|
||||
): void {
|
||||
this._phaseDefs = this._phaseDefs.map((d) => (d.id === id ? { ...d, ...patch } : d));
|
||||
}
|
||||
|
||||
@@ -1084,6 +1113,33 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
@input=${(e: Event) => this._patchPhaseDef(d.id, { checklistText: (e.target as HTMLTextAreaElement).value })}
|
||||
></textarea>
|
||||
` : nothing}
|
||||
<label class="req-option phase-req-toggle">
|
||||
<input
|
||||
type="checkbox"
|
||||
.checked=${d.reqOverride}
|
||||
@change=${(e: Event) => this._patchPhaseDef(d.id, { reqOverride: (e.target as HTMLInputElement).checked })}
|
||||
/>
|
||||
<span>${t("phase_require_override", L)}</span>
|
||||
</label>
|
||||
${d.reqOverride ? html`
|
||||
<div class="required-completion phase-req-fields">
|
||||
${REQUIRED_COMPLETION_KEYS.map((field) => html`
|
||||
<label class="req-option">
|
||||
<input
|
||||
type="checkbox"
|
||||
.checked=${d.reqFields.includes(field)}
|
||||
@change=${(e: Event) => {
|
||||
const on = (e.target as HTMLInputElement).checked;
|
||||
const next = new Set(d.reqFields);
|
||||
if (on) next.add(field); else next.delete(field);
|
||||
this._patchPhaseDef(d.id, { reqFields: [...next] });
|
||||
}}
|
||||
/>
|
||||
<span>${t(REQUIRED_COMPLETION_LABELS[field], L)}</span>
|
||||
</label>
|
||||
`)}
|
||||
</div>
|
||||
` : nothing}
|
||||
</div>
|
||||
`)}
|
||||
<ha-button appearance="plain" @click=${this._addPhaseDef}>
|
||||
@@ -1196,6 +1252,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
data.enabled = this._enabled;
|
||||
data.last_performed = this._lastPerformed || null;
|
||||
data.nfc_tag_id = this._nfcTagId || null;
|
||||
data.require_tag_scan = this._requireTagScan;
|
||||
data.reading_unit = this._readingUnit.trim() || null;
|
||||
// Task phases (#139): always sent — null clears a removed cycle.
|
||||
{
|
||||
@@ -1210,8 +1267,19 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
const qty = parseFloat(d.partQty);
|
||||
links.push({ part_id: d.partId, quantity: Number.isFinite(qty) && qty > 0 ? qty : 1 });
|
||||
}
|
||||
links.push(...d.extraParts);
|
||||
// entry_id travels only when present (a shared pool, #111) — an
|
||||
// own-part link stays byte-identical to what shipped before.
|
||||
for (const l of d.extraParts) {
|
||||
links.push(
|
||||
l.entry_id
|
||||
? { part_id: l.part_id, quantity: l.quantity, entry_id: l.entry_id }
|
||||
: { part_id: l.part_id, quantity: l.quantity },
|
||||
);
|
||||
}
|
||||
if (links.length) def.consumes_parts = links;
|
||||
// Override on → send the list, EMPTY included ("demands nothing");
|
||||
// off → omit the key so the task-level list falls through.
|
||||
if (d.reqOverride) def.required_completion_fields = [...d.reqFields];
|
||||
defs[d.id] = def;
|
||||
}
|
||||
const seq = this._phaseSeq.filter((id) => id in defs);
|
||||
@@ -2432,7 +2500,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
<div class="dialog-title">${title}</div>
|
||||
<div class="content">
|
||||
${this._error ? html`<div class="error">${this._error}</div>` : nothing}
|
||||
${this._objectChoices.length > 0 ? html`
|
||||
${this._taskId === null && this._objectChoices.length > 0 ? html`
|
||||
<div class="select-row">
|
||||
<label>${t("object", L)}</label>
|
||||
<select
|
||||
@@ -2703,6 +2771,9 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
${this._renderAdaptiveSection(L)}
|
||||
<ms-textfield
|
||||
label="${t("notes_optional", L)}"
|
||||
multiline
|
||||
.rows=${3}
|
||||
.helper=${t("notes_markdown_hint", L)}
|
||||
.value=${this._notes}
|
||||
@input=${(e: Event) => (this._notes = (e.target as HTMLInputElement).value)}
|
||||
></ms-textfield>
|
||||
@@ -2751,6 +2822,15 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
</div>
|
||||
`
|
||||
}
|
||||
<label class="req-option">
|
||||
<input
|
||||
type="checkbox"
|
||||
.checked=${this._requireTagScan}
|
||||
@change=${(e: Event) => (this._requireTagScan = (e.target as HTMLInputElement).checked)}
|
||||
/>
|
||||
<span>${t("require_tag_scan", L)}</span>
|
||||
</label>
|
||||
${this._requireTagScan ? html`<div class="field-help">${t("require_tag_scan_help", L)}</div>` : nothing}
|
||||
<label class="toggle-row">
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -2923,6 +3003,15 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
min-height: 56px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
.phase-req-toggle {
|
||||
margin-top: 6px;
|
||||
font-size: 13px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.phase-req-fields {
|
||||
margin: 2px 0 0 22px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.phase-remove {
|
||||
--mdc-icon-button-size: 36px;
|
||||
color: var(--secondary-text-color);
|
||||
|
||||
Reference in new issue
Block a user