251 files

This commit is contained in:
Home Assistant Version Control
2026-10-16 12:38:25 +00:00
parent 3acf0cdf8d
commit 64eb0c647c
251 changed files with 6380 additions and 14560 deletions
@@ -57,6 +57,14 @@ class TaskMateParentDashboardCard extends LitElement {
return fn ? fn(this.hass, key, params) : key;
}
/** Avatar: child's photo → MDI avatar → default icon. */
_avatarGlyph(source, fallback = "mdi:account-circle", cls = "tm-face-img") {
const face = window.__taskmate_child_visual(source);
return face.kind === "image"
? html`<img class="${cls}" src="${face.url}" alt="" loading="lazy">`
: html`<ha-icon icon="${face.kind === "icon" ? face.icon : fallback}"></ha-icon>`;
}
// Build "Chore · Child · date" for the lightbox caption (missing parts drop).
_photoCaption(chore, child, iso) {
let when = "";
@@ -587,6 +595,9 @@ class TaskMateParentDashboardCard extends LitElement {
const attrs = (window.__taskmate_attrs && window.__taskmate_attrs(this.hass, this.config.entity)) || entity.attributes || {};
const children = attrs.children || [];
// Grown-ups (#1061) only drop out of the per-child lists; approvals and
// rotation still need every profile to name whoever did the job.
const family = (window.__taskmate_family_children ? window.__taskmate_family_children(children, this.config) : children);
const chores = attrs.chores || [];
const completions = attrs.todays_completions || [];
// Pending approvals come from the full pending list (not filtered to
@@ -646,11 +657,11 @@ class TaskMateParentDashboardCard extends LitElement {
</div>
<div class="tab-content">
${this._activeSection === "overview" ? this._renderOverview(children, chores, completions, pointsIcon, pointsName) : ''}
${this._activeSection === "overview" ? this._renderOverview(family, chores, completions, pointsIcon, pointsName) : ''}
${this._activeSection === "rotation" ? this._renderRotation(rotationChores, children, attrs) : ''}
${this._activeSection === "approvals" ? this._renderApprovals(pendingCompletions, children, chores, pointsIcon) : ''}
${this._activeSection === "claims" ? this._renderClaims(pendingRewardClaims, pointsIcon) : ''}
${this._activeSection === "points" ? this._renderPoints(children, pointsIcon, pointsName) : ''}
${this._activeSection === "points" ? this._renderPoints(family, pointsIcon, pointsName) : ''}
</div>
</ha-card>
`;
@@ -665,12 +676,14 @@ class TaskMateParentDashboardCard extends LitElement {
_designTone(i) { return `var(--tmd-c${(i % 6) + 1})`; }
_av(name, avatar, tone, size) {
const a = avatar || "";
const inner = a.startsWith("mdi:")
? html`<ha-icon icon="${a}"></ha-icon>`
: a
? html`<img src="${a}" alt="${name || ''}">`
// `source` is a child object (so the child's photo travels with
// the avatar) or a bare icon string; the shared helper accepts either.
_av(name, source, tone, size) {
const face = window.__taskmate_child_visual(source);
const inner = face.kind === "icon"
? html`<ha-icon icon="${face.icon}"></ha-icon>`
: face.kind === "image"
? html`<img src="${face.url}" alt="${name || ''}">`
: (name || "?").split(" ").map(w => w[0]).slice(0, 2).join("").toUpperCase();
return html`<div class="av" style="--av:${size}px;--ac:${tone}">${inner}</div>`;
}
@@ -728,6 +741,9 @@ class TaskMateParentDashboardCard extends LitElement {
const attrs = (window.__taskmate_attrs && window.__taskmate_attrs(this.hass, this.config.entity)) || entity.attributes || {};
const children = attrs.children || [];
// Grown-ups (#1061) only drop out of the per-child lists; approvals and
// rotation still need every profile to name whoever did the job.
const family = (window.__taskmate_family_children ? window.__taskmate_family_children(children, this.config) : children);
const chores = attrs.chores || [];
const completions = attrs.todays_completions || [];
const pendingCompletions = attrs.chore_completions || completions.filter(c => !c.approved);
@@ -762,11 +778,11 @@ class TaskMateParentDashboardCard extends LitElement {
let body;
const sec = this._activeSection;
if (sec === "overview") body = this._dOverview(design, children, chores, completions, attrs, pointsIcon);
if (sec === "overview") body = this._dOverview(design, family, chores, completions, attrs, pointsIcon);
else if (sec === "rotation") body = this._dRotation(design, rotationChores, children, attrs);
else if (sec === "approvals") body = this._dApprovals(design, pendingCompletions, children, chores, pointsIcon);
else if (sec === "claims") body = this._dClaims(design, pendingRewardClaims, pointsIcon);
else if (sec === "points") body = this._dPoints(design, children, pointsIcon, pointsName);
else if (sec === "points") body = this._dPoints(design, family, pointsIcon, pointsName);
return html`<ha-card class="tmd" style="--hd:${hd}">
${this._dHeader(hd, totalPending)}
@@ -832,7 +848,7 @@ class TaskMateParentDashboardCard extends LitElement {
if (design === "console") {
return html`
<div class="row pd-ov-cn" style="--ac:${tone}">
${this._av(child.name, child.avatar, tone, 34)}
${this._av(child.name, child, tone, 34)}
<div style="flex:1;min-width:0">
<div class="pd-name">${child.name} <span class="muted pd-mini">${approved}/${total}</span></div>
<div class="bar" style="margin-top:6px"><i style="width:${pct}%"></i></div>
@@ -844,7 +860,7 @@ class TaskMateParentDashboardCard extends LitElement {
const isParent = window.__taskmate_is_parent(this.hass);
return html`
<div class="row pd-ov-cp" style="--ac:${tone}">
${this._av(child.name, child.avatar, tone, 36)}
${this._av(child.name, child, tone, 36)}
<div style="flex:1;min-width:0">
<div class="row" style="justify-content:space-between">
<span class="pd-name-cp">${child.name}</span>
@@ -862,7 +878,7 @@ class TaskMateParentDashboardCard extends LitElement {
return html`
<div class="pd-ov-pl" style="--ac:${tone}">
<div class="row">
${this._av(child.name, child.avatar, tone, 42)}
${this._av(child.name, child, tone, 42)}
<div style="flex:1;min-width:0">
<div class="pd-name">${child.name}</div>
<div class="muted pd-sub">${_ltrNums(this._t('dashboard.chores_done', { done: approved, total }, `${approved} / ${total} chores done`))}</div>
@@ -899,7 +915,7 @@ class TaskMateParentDashboardCard extends LitElement {
@click="${() => this._handleReject(comp.completion_id)}">✕</button>`;
return html`
<div class="row pd-item ${isLoading ? 'loading' : ''}" style="--ac:${tone}">
${this._av(child?.name || '?', child?.avatar, tone, 36)}
${this._av(child?.name || '?', child, tone, 36)}
<div style="flex:1;min-width:0">
<div class="pd-item-title">${comp.chore_name || choreMap[comp.chore_id]?.name || this._t('common.unknown')}</div>
<div class="muted pd-item-sub">${child?.name || this._t('common.unknown')}${time ? ` · ${time}` : ""} · +${pts}</div>
@@ -955,7 +971,7 @@ class TaskMateParentDashboardCard extends LitElement {
const loading = !!this._loading[key];
return html`
<div class="row pd-item ${loading ? 'loading' : ''}" style="--ac:${tone}">
${this._av(currentChild?.name || '?', currentChild?.avatar || 'mdi:rotate-3d-variant', tone, 36)}
${this._av(currentChild?.name || '?', currentChild?.avatar ? currentChild : 'mdi:rotate-3d-variant', tone, 36)}
<div style="flex:1;min-width:0">
<div class="pd-item-title">${chore.name}</div>
<div class="muted pd-item-sub">${currentChild?.name || unknown} · ${chore.assignment_mode}${group ? ` · ${group.policy === 'sticky' ? '🔗' : '🔀'} ${group.name}` : ""}</div>
@@ -976,7 +992,7 @@ class TaskMateParentDashboardCard extends LitElement {
const tone = this._designTone(i);
return html`
<div class="row pd-item" style="--ac:${tone}">
${this._av(child.name, child.avatar, tone, 36)}
${this._av(child.name, child, tone, 36)}
<div style="flex:1;min-width:0">
<div class="pd-item-title">${child.name}</div>
<div class="muted pd-item-sub">${child.points} ${pointsName}</div>
@@ -1078,7 +1094,7 @@ class TaskMateParentDashboardCard extends LitElement {
return html`
<div class="child-tile ${isParent ? 'tm-expandable' : ''}">
<div class="child-avatar">
<ha-icon icon="${child.avatar || 'mdi:account-circle'}"></ha-icon>
${this._avatarGlyph(child)}
</div>
<div class="child-tile-main"
@click="${isParent ? () => { this._expanded = { ...this._expanded, [child.id]: !isOpen }; this.requestUpdate(); } : null}">
@@ -1160,7 +1176,7 @@ class TaskMateParentDashboardCard extends LitElement {
return html`
<div class="approval-item ${loading ? 'loading' : ''}">
<div class="approval-child-avatar" style="background: linear-gradient(135deg, #16a085, #1abc9c);">
<ha-icon icon="${currentChild?.avatar || 'mdi:rotate-3d-variant'}"></ha-icon>
${this._avatarGlyph(currentChild, "mdi:rotate-3d-variant")}
</div>
<div class="approval-info">
<span class="approval-chore">${chore.name}</span>
@@ -1211,7 +1227,7 @@ class TaskMateParentDashboardCard extends LitElement {
return html`
<div class="approval-item ${isLoading ? 'loading' : ''}">
<div class="approval-child-avatar">
<ha-icon icon="${child?.avatar || 'mdi:account-circle'}"></ha-icon>
${this._avatarGlyph(child)}
</div>
<div class="approval-info">
<div class="approval-chore">${comp.chore_name || chore?.name || this._t('common.unknown')}</div>
@@ -1294,7 +1310,7 @@ class TaskMateParentDashboardCard extends LitElement {
${children.map(child => html`
<div class="quick-points-row">
<div class="qp-avatar">
<ha-icon icon="${child.avatar || 'mdi:account-circle'}"></ha-icon>
${this._avatarGlyph(child)}
</div>
<span class="qp-name">${child.name}</span>
<span class="qp-points">
@@ -1490,6 +1506,7 @@ class TaskMateParentDashboardCardEditor extends LitElement {
_computeLabel = (entry) => {
const labels = {
show_adults: this._t('common.editor.show_adults'),
entity: this._t('dashboard.editor.entity_label'),
title: this._t('dashboard.editor.title_label'),
card_design: this._t('common.design.field_label'),
@@ -1501,6 +1518,7 @@ class TaskMateParentDashboardCardEditor extends LitElement {
_computeHelper = (entry) => {
const helpers = {
show_adults: this._t('common.editor.show_adults_helper'),
entity: this._t('dashboard.editor.entity_helper'),
quick_points_amount: this._t('dashboard.editor.quick_points_helper'),
};
@@ -1519,8 +1537,8 @@ class TaskMateParentDashboardCardEditor extends LitElement {
return html`
<ha-form
.hass=${this.hass}
.data=${data}
.schema=${this._buildSchema()}
.data=${this.config.show_adults === true ? { ...data, show_adults: true } : data}
.schema=${this._buildSchema().concat(window.__taskmate_adults_field ? window.__taskmate_adults_field(this.hass, this.config) : [])}
.computeLabel=${this._computeLabel}
.computeHelper=${this._computeHelper}
@value-changed=${this._formChanged}