185 files

This commit is contained in:
Home Assistant Version Control
2026-08-19 11:38:13 +00:00
parent 5f9330abd7
commit a718af6423
185 changed files with 16868 additions and 3088 deletions
@@ -18,24 +18,6 @@ const PANEL_VERSION = (() => {
return el ? sanitize(new URLSearchParams(el.src.split("?")[1] || "").get("v")) : "?";
})();
const TABS = [
{ id: "children", lk: "panel.tab_children" },
{ id: "activity", lk: "panel.tab_activity" },
{ id: "insights", lk: "panel.tab_insights" },
{ id: "chores", lk: "panel.tab_chores" },
{ id: "rewards", lk: "panel.tab_rewards" },
{ id: "penalties", lk: "panel.tab_penalties" },
{ id: "bonuses", lk: "panel.tab_bonuses" },
{ id: "groups", lk: "panel.tab_groups" },
{ id: "quests", lk: "panel.tab_quests" },
{ id: "challenges", lk: "panel.tab_challenges" },
{ id: "badges", lk: "panel.tab_badges" },
{ id: "templates", lk: "panel.tab_templates" },
{ id: "notifications", lk: "panel.tab_notifications" },
{ id: "audit", lk: "panel.tab_audit" },
{ id: "settings", lk: "panel.tab_settings", label: "⚙" },
];
const BADGE_METRICS = [
{ v: "total_points", lk: "badge.metric_total_points" },
{ v: "total_chores", lk: "badge.metric_total_chores" },
@@ -1181,12 +1163,29 @@ class TaskMatePanel extends HTMLElement {
async _doApproveAll() {
const pending = this._state?.pending_completions || [];
if (pending.length === 0) return;
if (this._approvingAll) return;
if (!confirm(this._t("panel.activity_approve_all_confirm", { count: pending.length }))) return;
const { ok, err } = await this._callWS({
type: "taskmate/approve_all_chores",
completion_ids: pending.map(c => c.id),
});
if (!ok) { this._showToast("err", this._t("panel.toast_approve_failed", { error: err })); return; }
// Acknowledge the tap straight away (#799). The batch is fast since #794,
// but not instant, and a button that looks untouched is indistinguishable
// from one that did nothing — which is how #794 came to be reported.
this._approvingAll = true;
this._render();
let result;
try {
result = await this._callWS({
type: "taskmate/approve_all_chores",
completion_ids: pending.map(c => c.id),
});
} finally {
// Cleared the moment the call settles, whatever it did — a button stuck
// spinning is worse than no busy state at all.
this._approvingAll = false;
}
if (!result.ok) {
this._showToast("err", this._t("panel.toast_approve_failed", { error: result.err }));
this._render();
return;
}
await this._fetchState();
this._showToast("ok", this._t("panel.activity_approve_all_done", { count: pending.length }));
}
@@ -1531,6 +1530,10 @@ class TaskMatePanel extends HTMLElement {
image_url: d.image_url || "",
points: Number(d.points) || 0,
assigned_to: d.assigned_to || [],
// The dialog tracks this via toggle-depends, but it was never sent, so a
// dependency could neither be saved nor cleared — _ws_update_chore only
// applies fields present in the message (#793).
depends_on: d.depends_on || [],
requires_approval: !!d.requires_approval,
time_category: d.time_category || "anytime",
claim_allowance_minutes: Math.max(0, Number(d.claim_allowance_minutes) || 0),
@@ -3120,7 +3123,9 @@ class TaskMatePanel extends HTMLElement {
? `<span class="tm-pill tm-pill-warn">${pendingCompletions.length + pendingClaims.length + pendingSwaps.length}</span>`
: `<span class="tm-pill tm-pill-success">${this._t("panel.activity_pending_all_clear")}</span>`}
${pendingCompletions.length > 0
? `<button type="button" class="tm-btn tm-btn-raised tm-btn-sm" data-act="approve-all-chores" style="margin-left:auto">${this._t("panel.activity_approve_all")}</button>`
? `<button type="button" class="tm-btn tm-btn-raised tm-btn-sm" data-act="approve-all-chores" style="margin-left:auto"${this._approvingAll ? " disabled" : ""}>${this._approvingAll
? `<span class="tm-btn-spinner"></span>${this._t("panel.activity_approve_all_busy")}`
: this._t("panel.activity_approve_all")}</button>`
: ""}
</h3>
${pendingCompletions.length === 0 && pendingClaims.length === 0 && pendingSwaps.length === 0 ? `
@@ -3434,7 +3439,7 @@ class TaskMatePanel extends HTMLElement {
const assigned = (q.assigned_to && q.assigned_to.length)
? children.filter(c => q.assigned_to.includes(c.id)).map(c => c.name)
: [this._t("panel.quest_all_children")];
const stepNames = steps.map((sid, i) => {
const stepNames = steps.map((sid) => {
const name = choreById[sid] ? choreById[sid].name : this._t("panel.quest_missing_chore");
return `<li>${this._esc(name)}</li>`;
}).join("");
@@ -6554,6 +6559,24 @@ class TaskMatePanel extends HTMLElement {
background: var(--tm-accent-soft);
border-color: var(--tm-accent);
}
.tm-btn:disabled {
opacity: 0.6;
cursor: default;
pointer-events: none;
}
@keyframes tm-spin { to { transform: rotate(360deg); } }
.tm-btn-spinner {
width: 12px; height: 12px;
border: 2px solid currentColor;
border-top-color: transparent;
border-radius: 50%;
animation: tm-spin 0.7s linear infinite;
flex-shrink: 0;
}
@media (prefers-reduced-motion: reduce) {
/* Still reads as busy — the ring just doesn't rotate. */
.tm-btn-spinner { animation: none; opacity: 0.7; }
}
.tm-btn-raised {
background: var(--tm-accent);
color: var(--text-primary-color, #fff);