185 files
This commit is contained in:
1 parent
5f9330abd7
commit
a718af6423
185 files changed
+16868
-3088
No files matched your search
@@ -1144,6 +1144,23 @@ class TaskMateChildCard extends LitElement {
|
||||
|
||||
.recurrence-label ha-icon { --mdc-icon-size: 12px; }
|
||||
|
||||
/* Dependency-blocked (#793). Under dim the row stays readable so a child
|
||||
can see what unlocks next; show leaves it looking normal, but either
|
||||
way the tap is refused in the handler. */
|
||||
.chore-card.dependency-blocked {
|
||||
opacity: 0.5;
|
||||
filter: grayscale(0.5);
|
||||
}
|
||||
.dependency-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.dependency-label ha-icon { --mdc-icon-size: 12px; }
|
||||
|
||||
|
||||
/* Recurring chore not yet available — dimmed/greyed */
|
||||
.chore-card.recurrence-unavailable {
|
||||
@@ -1272,6 +1289,11 @@ class TaskMateChildCard extends LitElement {
|
||||
.pre-tile-icon ha-icon { --mdc-icon-size: 64px; color: var(--primary-color); }
|
||||
.pre-tile-stars { display: flex; gap: 2px; }
|
||||
.pre-tile-stars ha-icon { --mdc-icon-size: 18px; color: #f1c40f; }
|
||||
.pre-tile-points {
|
||||
display: flex; align-items: center; gap: 4px;
|
||||
font-size: 1.15rem; font-weight: 700; color: var(--primary-text-color);
|
||||
}
|
||||
.pre-tile-points ha-icon { --mdc-icon-size: 20px; color: #f1c40f; }
|
||||
.pre-tile-label {
|
||||
font-size: 0.85rem; font-weight: 700; text-align: center;
|
||||
color: var(--secondary-text-color); line-height: 1.2;
|
||||
@@ -1282,6 +1304,7 @@ class TaskMateChildCard extends LitElement {
|
||||
}
|
||||
.pre-tile.done .pre-tile-icon ha-icon { opacity: 0.35; }
|
||||
.pre-tile.done .pre-tile-stars { opacity: 0.35; }
|
||||
.pre-tile.done .pre-tile-points { opacity: 0.35; }
|
||||
.pre-tile-tick {
|
||||
position: absolute; inset: 0;
|
||||
display: grid; place-items: center;
|
||||
@@ -2418,8 +2441,8 @@ class TaskMateChildCard extends LitElement {
|
||||
${this._renderRoulette(child, childChores, pointsIcon)}
|
||||
${this.config.pre_reader === true ? html`
|
||||
<div class="pre-reader-grid">
|
||||
${childChores.map((chore, index) =>
|
||||
this._renderPreReaderTile(chore, child, todaysCompletions, index))}
|
||||
${childChores.map((chore) =>
|
||||
this._renderPreReaderTile(chore, child, pointsIcon, todaysCompletions))}
|
||||
</div>
|
||||
` : childChores.map((chore, index) => html`
|
||||
${chore.task_type === 'timed'
|
||||
@@ -2626,13 +2649,21 @@ class TaskMateChildCard extends LitElement {
|
||||
|
||||
const dueDaysMode = this.config.due_days_mode || "hide";
|
||||
const elapsedTimeMode = this.config.elapsed_time_mode || "dim";
|
||||
const dependencyMode = this.config.dependency_mode || "hide";
|
||||
const recurrenceDoneMode = this.config.recurrence_done_mode || "dim";
|
||||
|
||||
// Annotate each chore with done-state + handlers, preserving the classic path's logic.
|
||||
const rows = childChores.map((chore, i) => {
|
||||
const { done, completions } = this._isChoreDone(chore, child, todaysCompletions);
|
||||
const loading = !!this._loading[chore.id];
|
||||
// Blocked chores only reach here under dim/show — hide is filtered
|
||||
// upstream. Either way the tap must be refused (#793).
|
||||
const blocked = !!chore._isDependencyBlocked && !done;
|
||||
// Recurring chore still inside its cooldown. Excluded when done so the
|
||||
// undo chip stays live, exactly as on the classic row.
|
||||
const recLocked = !!chore._isRecurrenceLocked && !done;
|
||||
const onAct = () => {
|
||||
if (loading) return;
|
||||
if (loading || blocked || recLocked) return;
|
||||
if (done) this._handleUndo(chore, child, completions);
|
||||
else this._handleComplete(chore, child);
|
||||
};
|
||||
@@ -2642,10 +2673,12 @@ class TaskMateChildCard extends LitElement {
|
||||
const dimmed = !done && (
|
||||
(notDueToday && dueDaysMode === "dim") ||
|
||||
(chore._isTimeElapsed && elapsedTimeMode === "dim") ||
|
||||
(blocked && dependencyMode === "dim") ||
|
||||
(chore._isRecurrenceLocked && recurrenceDoneMode === "dim") ||
|
||||
chore._isLockedPreview === true
|
||||
);
|
||||
return {
|
||||
chore, child, done, loading, onAct, index: i, dimmed,
|
||||
chore, child, done, loading, onAct, index: i, dimmed, blocked, recLocked,
|
||||
tone: this._designTone(i),
|
||||
glyph: this._choreGlyph(chore),
|
||||
points: chore.effective_points ?? chore.points,
|
||||
@@ -2683,15 +2716,15 @@ class TaskMateChildCard extends LitElement {
|
||||
const body = this.config.pre_reader === true
|
||||
? html`
|
||||
<div class="pre-reader-grid">
|
||||
${childChores.map((chore, index) =>
|
||||
this._renderPreReaderTile(chore, child, todaysCompletions, index))}
|
||||
${childChores.map((chore) =>
|
||||
this._renderPreReaderTile(chore, child, pointsIcon, todaysCompletions))}
|
||||
</div>`
|
||||
: design === "playroom" ? this._designPlayroom(child, rows, remaining, tone) :
|
||||
design === "console" ? this._designConsole(child, rows, remaining, tone) :
|
||||
this._designCleanpro(child, rows, remaining, tone);
|
||||
|
||||
return html`<ha-card class="tmd" style="--hd:${hd}">
|
||||
${this._designHeaderFull(child, design, remaining, rows.length, tone, pendingPoints, pointsIcon)}
|
||||
${this._designHeaderFull(child, design, remaining, rows.length, tone, pendingPoints)}
|
||||
<div class="tmd-bd">
|
||||
${attrs.vacation_active ? html`
|
||||
<div class="tmd-vacation">
|
||||
@@ -2742,7 +2775,7 @@ class TaskMateChildCard extends LitElement {
|
||||
}
|
||||
|
||||
/** Designed header: avatar/title, remaining pill, pending-points chip. */
|
||||
_designHeaderFull(child, design, remaining, total, tone, pendingPoints, pointsIcon) {
|
||||
_designHeaderFull(child, design, remaining, total, tone, pendingPoints) {
|
||||
const title = design === "console"
|
||||
? `${(child.name || "").toUpperCase()} · ${this._t("child.todays_chores").toUpperCase()}`
|
||||
: this._t("points_display.single_title", { name: child.name });
|
||||
@@ -2775,11 +2808,21 @@ class TaskMateChildCard extends LitElement {
|
||||
/** Shared meta tags (mandatory / photo / description) for a designed chore row. */
|
||||
_designChoreMeta(r) {
|
||||
const showDesc = this.config.show_description === true && r.chore.description;
|
||||
// A blocked chore says what unlocks it here too — the classic row carries
|
||||
// the same label, and a dimmed row with no reason is just confusing (#793).
|
||||
const depNames = r.blocked ? (r.chore._dependencyNames || []) : [];
|
||||
// Same reason label the classic row carries, so a dimmed designed row is
|
||||
// not just mysteriously grey (#803).
|
||||
const recLabel = r.recLocked
|
||||
? (r.chore.recurrence ? r.chore.recurrence.replace(/_/g, " ") : this._t("child.recurring"))
|
||||
: "";
|
||||
return html`
|
||||
${r.mandatory || r.photo ? html`
|
||||
${r.mandatory || r.photo || depNames.length || recLabel ? html`
|
||||
<div class="tmd-meta">
|
||||
${r.mandatory ? html`<span class="tmd-tag mandatory">⚠ ${this._t("child.mandatory")}</span>` : ""}
|
||||
${r.photo ? html`<span class="tmd-tag photo">📷 ${this._t("child.photo_needed")}</span>` : ""}
|
||||
${depNames.length ? html`<span class="tmd-tag">🔒 ${this._t("child.blocked_by_dependency", { chores: depNames.join(", ") })}</span>` : ""}
|
||||
${recLabel ? html`<span class="tmd-tag">🕒 ${recLabel}</span>` : ""}
|
||||
</div>` : ""}
|
||||
${showDesc ? html`<div class="tmd-desc">${r.chore.description}</div>` : ""}`;
|
||||
}
|
||||
@@ -2795,7 +2838,7 @@ class TaskMateChildCard extends LitElement {
|
||||
|
||||
_designDoneBtn(r, label, cls) {
|
||||
return html`<button class="btn ${cls || ""}"
|
||||
?disabled=${r.loading}
|
||||
?disabled=${r.loading || r.blocked || r.recLocked}
|
||||
@click=${(e) => { e.stopPropagation(); r.onAct(); }}>${label}</button>`;
|
||||
}
|
||||
|
||||
@@ -2820,7 +2863,7 @@ class TaskMateChildCard extends LitElement {
|
||||
return this._renderBonusSubtasks(r.chore, r.child, r.pointsIcon, r.todaysCompletions);
|
||||
}
|
||||
|
||||
_designPlayroom(child, rows, remaining, tone) {
|
||||
_designPlayroom(child, rows, _remaining, _tone) {
|
||||
if (rows.length === 0) return html`<div class="tmd-empty">${this._t("child.all_done")}</div>`;
|
||||
return html`<div class="tmd-chores">
|
||||
${rows.map(r => r.timed ? this._designTimed(r) : html`
|
||||
@@ -2840,7 +2883,7 @@ class TaskMateChildCard extends LitElement {
|
||||
</div>`;
|
||||
}
|
||||
|
||||
_designConsole(child, rows, remaining, tone) {
|
||||
_designConsole(child, rows, _remaining, _tone) {
|
||||
if (rows.length === 0) return html`<div class="tmd-empty">${this._t("child.all_done")}</div>`;
|
||||
return html`<div class="grid">
|
||||
${rows.map(r => r.timed ? this._designTimed(r) : html`
|
||||
@@ -2862,7 +2905,7 @@ class TaskMateChildCard extends LitElement {
|
||||
</div>`;
|
||||
}
|
||||
|
||||
_designCleanpro(child, rows, remaining, tone) {
|
||||
_designCleanpro(child, rows, _remaining, _tone) {
|
||||
if (rows.length === 0) return html`<div class="tmd-empty">${this._t("child.all_done")}</div>`;
|
||||
return html`<div class="tmd-checklist">
|
||||
${rows.map(r => r.timed ? this._designTimed(r) : html`
|
||||
@@ -2951,11 +2994,8 @@ class TaskMateChildCard extends LitElement {
|
||||
|
||||
_filterAndSortChores(chores, child) {
|
||||
const childId = String(child.id || "");
|
||||
const childName = child.name;
|
||||
const choreOrder = child.chore_order || [];
|
||||
|
||||
// Debug logging to diagnose assignment filtering issues
|
||||
|
||||
// Get today's day of week from sensor (set by backend) or compute client-side
|
||||
const entity = this.hass?.states?.[this.config.entity];
|
||||
const attrs = (window.__taskmate_attrs && window.__taskmate_attrs(this.hass, this.config.entity)) || entity?.attributes || {};
|
||||
@@ -2965,6 +3005,18 @@ class TaskMateChildCard extends LitElement {
|
||||
const dueDaysMode = this.config.due_days_mode || 'hide';
|
||||
const showDueDaysOnly = this.config.show_due_days_only !== false;
|
||||
const elapsedTimeMode = this.config.elapsed_time_mode || 'dim';
|
||||
// Hide by default: the backend simply reports a dependency-blocked chore
|
||||
// as unavailable, so hiding is what matches it (#793).
|
||||
const dependencyMode = this.config.dependency_mode || 'hide';
|
||||
const recurrenceDoneMode = this.config.recurrence_done_mode || 'dim';
|
||||
const allCompletionsToday = attrs.todays_completions || [];
|
||||
// Availability comes from the backend's own matrix (#803). The card cannot
|
||||
// work a recurrence window out for itself: last_completed is not exposed,
|
||||
// `recurrence` is omitted from the payload when it is the default weekly,
|
||||
// and the maths is calendar-month aware. A missing matrix (older backend,
|
||||
// or a sensor that hasn't published yet) has to read as available, or the
|
||||
// card would blank itself.
|
||||
const availability = attrs.chore_availability || {};
|
||||
|
||||
// First, filter chores for this child and time category
|
||||
const filteredChores = chores.filter(chore => {
|
||||
@@ -3112,6 +3164,42 @@ class TaskMateChildCard extends LitElement {
|
||||
chore._isDueToday = isDueToday;
|
||||
chore._hasDueDays = hasDueDays;
|
||||
|
||||
// Chore dependencies (#793). Mirrors is_chore_available_for_child():
|
||||
// a prerequisite counts as satisfied only when this same child has an
|
||||
// approved, non-bonus completion of it today. todays_completions is
|
||||
// already scoped to today, so the date check is implicit. A looser rule
|
||||
// here would unlock a chore the backend then refuses to complete.
|
||||
const dependsOn = Array.isArray(chore.depends_on) ? chore.depends_on : [];
|
||||
chore._isDependencyBlocked = dependsOn.length > 0 && !dependsOn.every(
|
||||
(depId) => allCompletionsToday.some(
|
||||
(comp) => comp.chore_id === depId
|
||||
&& String(comp.child_id) === childId
|
||||
&& comp.approved
|
||||
&& !comp.bonus_subtask_id
|
||||
)
|
||||
);
|
||||
chore._dependencyNames = chore._isDependencyBlocked
|
||||
? dependsOn
|
||||
.map((depId) => (chores.find((c) => c.id === depId) || {}).name)
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
if (chore._isDependencyBlocked && dependencyMode === 'hide') return false;
|
||||
|
||||
// Recurring chores in their cooldown window (#803). Both of these are
|
||||
// read by the render path, which until now found them undefined.
|
||||
chore._isRecurring = (chore.schedule_mode || 'specific_days') === 'recurring';
|
||||
const availableNow = (availability[chore.id] || {})[childId] !== false;
|
||||
// Completing a recurring chore makes it unavailable at once, so hiding on
|
||||
// availability alone would vanish it the instant it is ticked — no done
|
||||
// state, no way to undo. The mode is about the days *after* that.
|
||||
const completedToday = allCompletionsToday.some(
|
||||
(comp) => comp.chore_id === chore.id
|
||||
&& (String(comp.child_id) === childId || comp.child_id === '__parent__')
|
||||
&& !comp.bonus_subtask_id
|
||||
);
|
||||
chore._isRecurrenceLocked = chore._isRecurring && !availableNow && !completedToday;
|
||||
if (chore._isRecurrenceLocked && recurrenceDoneMode === 'hide') return false;
|
||||
|
||||
// Mark whether this chore's time period has elapsed (grace-aware).
|
||||
chore._isTimeElapsed = this._isTimePeriodElapsed(chore);
|
||||
|
||||
@@ -3503,8 +3591,12 @@ class TaskMateChildCard extends LitElement {
|
||||
* Pre-reader mode (#683): a picture-only tile for children who can't read
|
||||
* yet. No chore name, no numbers — a big icon, a row of stars for the
|
||||
* points, and a huge tick when it's done.
|
||||
*
|
||||
* `pre_reader_points` (#795) swaps the stars for the value itself, for
|
||||
* families whose children can already read a number and who found the
|
||||
* rounded star count contradicted the balance on the rest of the dashboard.
|
||||
*/
|
||||
_renderPreReaderTile(chore, child, todaysCompletions = [], choreIndex = 0) {
|
||||
_renderPreReaderTile(chore, child, pointsIcon, todaysCompletions = []) {
|
||||
const dailyLimit = chore.daily_limit || 1;
|
||||
// Counted the same way the standard row does: pending completions hold a
|
||||
// place too, and a parent completing on behalf lands under "__parent__".
|
||||
@@ -3515,11 +3607,15 @@ class TaskMateChildCard extends LitElement {
|
||||
);
|
||||
const isDone = childCompletionsToday.length >= dailyLimit;
|
||||
const isLoading = this._loading[chore.id];
|
||||
const available = chore._isAvailableForChild !== false && !chore._isLockedPreview;
|
||||
const available = chore._isAvailableForChild !== false && !chore._isLockedPreview
|
||||
&& !chore._isDependencyBlocked;
|
||||
|
||||
// Stars, not digits: a 4-year-old can count pictures, not read "+3".
|
||||
// The count is deliberately lossy — it rounds and clamps to 1..5 — which
|
||||
// is why showing the number instead is opt-in rather than a second row.
|
||||
const points = chore.effective_points ?? chore.points ?? 0;
|
||||
const stars = Math.max(1, Math.min(5, Math.round(points / 2) || 1));
|
||||
const showPoints = this.config.pre_reader_points === true;
|
||||
|
||||
const v = window.__taskmate_chore_visual(chore);
|
||||
const icon = (v.kind === "icon" ? v.icon : "")
|
||||
@@ -3542,9 +3638,15 @@ class TaskMateChildCard extends LitElement {
|
||||
: html`<ha-icon icon="${icon}"></ha-icon>`}
|
||||
</div>
|
||||
${isDone ? html`<div class="pre-tile-tick"><ha-icon icon="mdi:check-bold"></ha-icon></div>` : ''}
|
||||
<div class="pre-tile-stars">
|
||||
${Array.from({ length: stars }, () => html`<ha-icon icon="mdi:star"></ha-icon>`)}
|
||||
</div>
|
||||
${showPoints
|
||||
? html`
|
||||
<div class="pre-tile-points">
|
||||
<ha-icon icon="${pointsIcon}"></ha-icon>+${points}
|
||||
</div>`
|
||||
: html`
|
||||
<div class="pre-tile-stars">
|
||||
${Array.from({ length: stars }, () => html`<ha-icon icon="mdi:star"></ha-icon>`)}
|
||||
</div>`}
|
||||
${this.config.pre_reader_labels === true
|
||||
? html`<div class="pre-tile-label">${chore.name}</div>` : ''}
|
||||
</button>
|
||||
@@ -3592,8 +3694,6 @@ class TaskMateChildCard extends LitElement {
|
||||
// This is defensive - we'd rather show "completed" incorrectly than allow double-completions
|
||||
// The optimistic completion will be cleaned up once we verify the server state reflects it
|
||||
if (hasOptimisticCompletion) {
|
||||
// Calculate how many optimistic completions we've tracked that aren't yet in the data
|
||||
const optimisticCount = optimisticData.count || 1;
|
||||
|
||||
// Get timestamps from actual completions for this chore/child today
|
||||
const actualTimestamps = childCompletionsToday.map(comp =>
|
||||
@@ -3616,8 +3716,6 @@ class TaskMateChildCard extends LitElement {
|
||||
|
||||
const isCompletedForToday = completionsToday >= dailyLimit;
|
||||
|
||||
// Check if the most recent completion is pending approval
|
||||
const hasPendingCompletion = childCompletionsToday.some((comp) => !comp.approved) || hasOptimisticCompletion;
|
||||
|
||||
// Chore number (1-indexed for display) and color class (cycle through 8 colors)
|
||||
const choreNumber = choreIndex + 1;
|
||||
@@ -3626,11 +3724,21 @@ class TaskMateChildCard extends LitElement {
|
||||
// Click handler for the entire row
|
||||
const notDueToday = chore._hasDueDays && !chore._isDueToday && this.config.due_days_mode === 'dim';
|
||||
const recurrenceDoneMode = this.config.recurrence_done_mode || 'dim';
|
||||
const notAvailableRecurrence = chore._isRecurring && !chore._isAvailableForChild && recurrenceDoneMode === 'dim';
|
||||
// The tap is refused whenever the chore is locked: async_complete_chore
|
||||
// no-ops a recurring chore inside its window, so a clickable row under
|
||||
// `show` would just swallow the tap (#805). Only the dim STYLING is
|
||||
// mode-gated. Same split dependency_mode uses.
|
||||
const recurrenceLocked = !!chore._isRecurrenceLocked && !isCompletedForToday;
|
||||
const notAvailableRecurrence = recurrenceLocked && recurrenceDoneMode === 'dim';
|
||||
// Elapsed: only dim incomplete chores — completed ones keep their green "done" style
|
||||
const elapsedTimeMode = this.config.elapsed_time_mode || 'dim';
|
||||
const timeElapsed = chore._isTimeElapsed && !isCompletedForToday && elapsedTimeMode === 'dim';
|
||||
const isLockedPreview = !!chore._isLockedPreview && !isCompletedForToday;
|
||||
// Under dim/show the blocked chore is still on screen, so the tap has to be
|
||||
// refused here — the backend would reject the completion anyway (#793).
|
||||
// Only `dim` greys it out; `show` is meant to look untouched.
|
||||
const depBlocked = !!chore._isDependencyBlocked && !isCompletedForToday;
|
||||
const depDimmed = depBlocked && (this.config.dependency_mode || 'hide') === 'dim';
|
||||
const periodStartHour = isLockedPreview
|
||||
? (this._getPeriodHours(chore.time_category)?.[0] ?? null)
|
||||
: null;
|
||||
@@ -3640,8 +3748,9 @@ class TaskMateChildCard extends LitElement {
|
||||
const handleRowClick = () => {
|
||||
if (isLoading) return;
|
||||
if (notDueToday) return; // Dim mode — not interactive
|
||||
if (notAvailableRecurrence) return; // Recurrence window not open — not interactive
|
||||
if (recurrenceLocked) return; // Recurrence window not open — not interactive
|
||||
if (isLockedPreview) return; // Next-period preview — not yet claimable
|
||||
if (depBlocked) return; // Prerequisite chore not approved yet
|
||||
if (timeElapsed) return; // Time period passed — not interactive
|
||||
if (isCompletedForToday) {
|
||||
this._handleUndo(chore, child, childCompletionsToday);
|
||||
@@ -3649,7 +3758,7 @@ class TaskMateChildCard extends LitElement {
|
||||
this._handleComplete(chore, child);
|
||||
}
|
||||
};
|
||||
const isInteractive = !(isLoading || notDueToday || notAvailableRecurrence || isLockedPreview || timeElapsed);
|
||||
const isInteractive = !(isLoading || notDueToday || recurrenceLocked || isLockedPreview || depBlocked || timeElapsed);
|
||||
const handleRowKeyDown = (e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
@@ -3659,6 +3768,8 @@ class TaskMateChildCard extends LitElement {
|
||||
|
||||
const titleText = isLockedPreview
|
||||
? (lockedUntilLabel || this._t('child.chore_locked_until_generic'))
|
||||
: depBlocked
|
||||
? this._t('child.blocked_by_dependency', { chores: (chore._dependencyNames || []).join(', ') })
|
||||
: notDueToday
|
||||
? this._t('child.not_scheduled_for_today')
|
||||
: timeElapsed
|
||||
@@ -3669,7 +3780,7 @@ class TaskMateChildCard extends LitElement {
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="chore-card ${chore.mandatory ? "mandatory" : ""} ${isLoading ? "loading" : ""} ${isCelebrating ? "celebrating" : ""} ${isCompletedForToday ? "completed" : ""} ${notDueToday ? "not-due-today" : ""} ${notAvailableRecurrence ? "recurrence-unavailable" : ""} ${timeElapsed ? "time-elapsed" : ""} ${isLockedPreview ? "chore-locked" : ""}"
|
||||
class="chore-card ${chore.mandatory ? "mandatory" : ""} ${isLoading ? "loading" : ""} ${isCelebrating ? "celebrating" : ""} ${isCompletedForToday ? "completed" : ""} ${notDueToday ? "not-due-today" : ""} ${notAvailableRecurrence ? "recurrence-unavailable" : ""} ${timeElapsed ? "time-elapsed" : ""} ${isLockedPreview ? "chore-locked" : ""} ${depDimmed ? "dependency-blocked" : ""}"
|
||||
role="button"
|
||||
tabindex="${isInteractive ? '0' : '-1'}"
|
||||
aria-disabled="${isInteractive ? 'false' : 'true'}"
|
||||
@@ -3689,7 +3800,13 @@ class TaskMateChildCard extends LitElement {
|
||||
${this.config.show_description && chore.description ? html`
|
||||
<div class="chore-description">${chore.description}</div>
|
||||
` : ''}
|
||||
${chore._isRecurring && !chore._isAvailableForChild ? html`
|
||||
${depBlocked && (chore._dependencyNames || []).length ? html`
|
||||
<div class="dependency-label">
|
||||
<ha-icon icon="mdi:lock-outline"></ha-icon>
|
||||
${this._t('child.blocked_by_dependency', { chores: chore._dependencyNames.join(', ') })}
|
||||
</div>
|
||||
` : ''}
|
||||
${chore._isRecurrenceLocked ? html`
|
||||
<div class="recurrence-label">
|
||||
<ha-icon icon="mdi:clock-outline"></ha-icon>
|
||||
${chore.recurrence ? chore.recurrence.replace(/_/g, ' ') : this._t('child.recurring')}
|
||||
@@ -3728,6 +3845,9 @@ class TaskMateChildCard extends LitElement {
|
||||
|
||||
const colorClass = `color-${choreIndex % 8}`;
|
||||
const choreNumber = choreIndex + 1;
|
||||
// A blocked timed chore must not be startable under dim/show (#793).
|
||||
// Pause/stop stay live so a session already running can still be ended.
|
||||
const depBlocked = !!chore._isDependencyBlocked;
|
||||
|
||||
// Calculate elapsed seconds
|
||||
let elapsed = session ? (session.total_seconds_today || 0) : 0;
|
||||
@@ -3759,6 +3879,7 @@ class TaskMateChildCard extends LitElement {
|
||||
const nearCap = maxMin > 0 && capPct >= 85;
|
||||
|
||||
const handleStart = (e) => {
|
||||
if (depBlocked) { e.stopPropagation(); return; }
|
||||
e.stopPropagation();
|
||||
if (isLoading) return;
|
||||
this._loading = { ...this._loading, [`timed_${chore.id}`]: true };
|
||||
@@ -3829,7 +3950,7 @@ class TaskMateChildCard extends LitElement {
|
||||
|
||||
<div class="timer-actions">
|
||||
${state === 'idle' ? html`
|
||||
<button class="timer-btn start" @click="${handleStart}" ?disabled="${isLoading}">
|
||||
<button class="timer-btn start" @click="${handleStart}" ?disabled="${isLoading || depBlocked}">
|
||||
<span class="btn-icon">▶</span>
|
||||
${this._t('child.start') || 'START'}
|
||||
</button>
|
||||
@@ -3845,7 +3966,7 @@ class TaskMateChildCard extends LitElement {
|
||||
</button>
|
||||
` : ''}
|
||||
${state === 'paused' ? html`
|
||||
<button class="timer-btn resume" @click="${handleStart}" ?disabled="${isLoading}">
|
||||
<button class="timer-btn resume" @click="${handleStart}" ?disabled="${isLoading || depBlocked}">
|
||||
<span class="btn-icon">▶</span>
|
||||
${this._t('child.resume') || 'RESUME'}
|
||||
</button>
|
||||
@@ -4708,6 +4829,19 @@ class TaskMateChildCardEditor extends LitElement {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'dependency_mode',
|
||||
selector: {
|
||||
select: {
|
||||
options: [
|
||||
{ value: 'hide', label: this._t('child.editor.dependency_hide') },
|
||||
{ value: 'dim', label: this._t('child.editor.dependency_dim') },
|
||||
{ value: 'show', label: this._t('child.editor.dependency_show') },
|
||||
],
|
||||
mode: 'dropdown',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'elapsed_time_mode',
|
||||
selector: {
|
||||
@@ -4725,6 +4859,7 @@ class TaskMateChildCardEditor extends LitElement {
|
||||
{ name: 'show_description', selector: { boolean: {} } },
|
||||
{ name: 'pre_reader', selector: { boolean: {} } },
|
||||
{ name: 'pre_reader_labels', selector: { boolean: {} } },
|
||||
{ name: 'pre_reader_points', selector: { boolean: {} } },
|
||||
{ name: 'debug', selector: { boolean: {} } },
|
||||
];
|
||||
}
|
||||
@@ -4737,11 +4872,13 @@ class TaskMateChildCardEditor extends LitElement {
|
||||
card_design: this._t('common.design.field_label'),
|
||||
due_days_mode: this._t('child.editor.chores_not_due_today'),
|
||||
recurrence_done_mode: this._t('child.editor.recurring_when_completed'),
|
||||
dependency_mode: this._t('child.editor.dependency_mode'),
|
||||
elapsed_time_mode: this._t('child.editor.missed_time_chores'),
|
||||
show_countdown: this._t('child.editor.show_countdown'),
|
||||
show_description: this._t('child.editor.show_description'),
|
||||
pre_reader: this._t('child.editor.pre_reader'),
|
||||
pre_reader_labels: this._t('child.editor.pre_reader_labels'),
|
||||
pre_reader_points: this._t('child.editor.pre_reader_points'),
|
||||
debug: this._t('child.editor.show_debug'),
|
||||
};
|
||||
return labels[entry.name] ?? entry.name;
|
||||
@@ -4754,6 +4891,7 @@ class TaskMateChildCardEditor extends LitElement {
|
||||
time_category: this._t('child.editor.time_category_helper'),
|
||||
due_days_mode: this._t('child.editor.due_days_helper'),
|
||||
recurrence_done_mode: this._t('child.editor.recurrence_helper'),
|
||||
dependency_mode: this._t('child.editor.dependency_helper'),
|
||||
elapsed_time_mode: this._t('child.editor.elapsed_helper'),
|
||||
};
|
||||
return helpers[entry.name] ?? '';
|
||||
@@ -4769,11 +4907,13 @@ class TaskMateChildCardEditor extends LitElement {
|
||||
card_design: this.config.card_design || 'global',
|
||||
due_days_mode: this.config.due_days_mode || 'hide',
|
||||
recurrence_done_mode: this.config.recurrence_done_mode || 'dim',
|
||||
dependency_mode: this.config.dependency_mode || 'hide',
|
||||
elapsed_time_mode: this.config.elapsed_time_mode || 'dim',
|
||||
show_countdown: this.config.show_countdown !== false,
|
||||
show_description: this.config.show_description === true,
|
||||
pre_reader: this.config.pre_reader === true,
|
||||
pre_reader_labels: this.config.pre_reader_labels === true,
|
||||
pre_reader_points: this.config.pre_reader_points === true,
|
||||
debug: this.config.debug === true,
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user