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
+10 -1
View File
@@ -69,6 +69,7 @@
"approvals.all_caught_up": "Alles aufgeholt!",
"approvals.approve": "Genehmigen",
"approvals.approve_all": "Alle genehmigen",
"approvals.approve_all_busy": "Wird genehmigt…",
"approvals.approve_all_confirm": "{count} ausstehende Aufgaben genehmigen?",
"approvals.card_description": "Eine Karte zum Verwalten ausstehender Aufgabengenehmigungen für TaskMate",
"approvals.card_name": "TaskMate-Genehmigungen",
@@ -229,6 +230,12 @@
"child.editor.child": "Kind",
"child.editor.child_helper": "Für welches Kind ist diese Karte?",
"child.editor.chores_not_due_today": "Aufgaben, die heute nicht fällig sind",
"child.editor.dependency_mode": "Aufgaben, die auf andere warten",
"child.editor.dependency_hide": "Ausblenden – erst nach Freischaltung zeigen",
"child.editor.dependency_dim": "Gedimmt – ausgegraut anzeigen, nicht interaktiv",
"child.editor.dependency_show": "Anzeigen – normal zeigen (weiterhin nicht antippbar)",
"child.editor.dependency_helper": "Was angezeigt wird, wenn eine Aufgabe von einer anderen abhängt, die heute noch nicht genehmigt ist",
"child.blocked_by_dependency": "Zuerst {chores} erledigen",
"child.editor.due_days_dim": "Gedimmt – ausgegraut anzeigen, nicht interaktiv",
"child.editor.due_days_helper": "Gilt für Aufgaben, bei denen due_days festgelegt ist, wenn der heutige Tag nicht geplant ist",
"child.editor.due_days_hide": "Ausblenden – nicht anzeigen",
@@ -241,7 +248,7 @@
"child.editor.recurrence_dim": "Gedimmt – ausgegraut anzeigen, nicht interaktiv",
"child.editor.recurrence_helper": "Was angezeigt werden soll, wenn eine wiederkehrende Aufgabe abgeschlossen wurde und das Fenster noch nicht zurückgesetzt wurde",
"child.editor.recurrence_hide": "Ausblenden – erst wieder anzeigen, wenn es wieder verfügbar ist",
"child.editor.recurrence_show": "Anzeigen – trotzdem normal anzeigen",
"child.editor.recurrence_show": "Anzeigen – normal zeigen (weiterhin nicht antippbar)",
"child.editor.recurring_when_completed": "Wiederkehrende Aufgaben – nach Abschluss",
"child.editor.select_child": "Wählen Sie ein Kind aus...",
"child.editor.show_countdown": "Countdown zum Zurücksetzen neben „Heutige Aufgaben“ anzeigen",
@@ -420,6 +427,7 @@
"overview.pending_count": "{count} ausstehend",
"panel.activity_approve": "Genehmigen",
"panel.activity_approve_all": "Alle genehmigen",
"panel.activity_approve_all_busy": "Wird genehmigt…",
"panel.activity_approve_all_confirm": "{count} ausstehende Aufgaben genehmigen?",
"panel.activity_approve_all_done": "{count} Aufgaben genehmigt",
"panel.activity_audit_log": "Protokoll",
@@ -1738,5 +1746,6 @@
"panel.chore_image_bad_type": "Wähle ein JPEG-, PNG- oder WebP-Bild.",
"child.editor.pre_reader": "Nur-Bilder-Modus (für kleine Kinder)",
"child.editor.pre_reader_labels": "Namen im Bildmodus anzeigen",
"child.editor.pre_reader_points": "Punktzahl statt Sterne im Bildmodus anzeigen",
"common.design.accessible": "Barrierefrei"
}
@@ -69,6 +69,7 @@
"approvals.all_caught_up": "All caught up!",
"approvals.approve": "Approve",
"approvals.approve_all": "Approve all",
"approvals.approve_all_busy": "Approving…",
"approvals.approve_all_confirm": "Approve {count} pending chores?",
"approvals.card_description": "A card to manage pending chore approvals for TaskMate",
"approvals.card_name": "TaskMate Approvals",
@@ -231,6 +232,12 @@
"child.editor.child": "Child",
"child.editor.child_helper": "Which child is this card for?",
"child.editor.chores_not_due_today": "Chores Not Due Today",
"child.editor.dependency_mode": "Chores Waiting On Another",
"child.editor.dependency_hide": "Hide — don't show until unlocked",
"child.editor.dependency_dim": "Dim — show greyed out, non-interactive",
"child.editor.dependency_show": "Show — show normally (still not tappable)",
"child.editor.dependency_helper": "What to show when a chore depends on another that isn't approved yet today",
"child.blocked_by_dependency": "Do {chores} first",
"child.editor.due_days_dim": "Dim — show greyed out, non-interactive",
"child.editor.due_days_helper": "Applies to chores with due_days set when today isn't scheduled",
"child.editor.due_days_hide": "Hide — don't show",
@@ -243,7 +250,7 @@
"child.editor.recurrence_dim": "Dim — show greyed out, non-interactive",
"child.editor.recurrence_helper": "What to show when a recurring chore has been completed and the window hasn't reset yet",
"child.editor.recurrence_hide": "Hide — don't show until available again",
"child.editor.recurrence_show": "Show — show normally regardless",
"child.editor.recurrence_show": "Show — show normally (still not tappable)",
"child.editor.recurring_when_completed": "Recurring Chores — When Completed",
"child.editor.select_child": "Select a child...",
"child.editor.show_countdown": "Show reset countdown beside \"Today's Chores\"",
@@ -427,6 +434,7 @@
"overview.pending_count": "{count} pending",
"panel.activity_approve": "Approve",
"panel.activity_approve_all": "Approve all",
"panel.activity_approve_all_busy": "Approving…",
"panel.activity_approve_all_confirm": "Approve {count} pending chores?",
"panel.activity_approve_all_done": "Approved {count} chores",
"panel.activity_audit_log": "Audit log",
@@ -1738,5 +1746,6 @@
"panel.chore_image_bad_type": "Choose a JPEG, PNG or WebP image.",
"child.editor.pre_reader": "Picture-only mode (for young children)",
"child.editor.pre_reader_labels": "Show names in picture mode",
"child.editor.pre_reader_points": "Show point values instead of stars in picture mode",
"common.design.accessible": "Accessible"
}
+10 -1
View File
@@ -69,6 +69,7 @@
"approvals.all_caught_up": "All caught up!",
"approvals.approve": "Approve",
"approvals.approve_all": "Approve all",
"approvals.approve_all_busy": "Approving…",
"approvals.approve_all_confirm": "Approve {count} pending chores?",
"approvals.card_description": "A card to manage pending chore approvals for TaskMate",
"approvals.card_name": "TaskMate Approvals",
@@ -231,6 +232,12 @@
"child.editor.child": "Child",
"child.editor.child_helper": "Which child is this card for?",
"child.editor.chores_not_due_today": "Chores Not Due Today",
"child.editor.dependency_mode": "Chores Waiting On Another",
"child.editor.dependency_hide": "Hide — don't show until unlocked",
"child.editor.dependency_dim": "Dim — show greyed out, non-interactive",
"child.editor.dependency_show": "Show — show normally (still not tappable)",
"child.editor.dependency_helper": "What to show when a chore depends on another that isn't approved yet today",
"child.blocked_by_dependency": "Do {chores} first",
"child.editor.due_days_dim": "Dim — show greyed out, non-interactive",
"child.editor.due_days_helper": "Applies to chores with due_days set when today isn't scheduled",
"child.editor.due_days_hide": "Hide — don't show",
@@ -243,7 +250,7 @@
"child.editor.recurrence_dim": "Dim — show greyed out, non-interactive",
"child.editor.recurrence_helper": "What to show when a recurring chore has been completed and the window hasn't reset yet",
"child.editor.recurrence_hide": "Hide — don't show until available again",
"child.editor.recurrence_show": "Show — show normally regardless",
"child.editor.recurrence_show": "Show — show normally (still not tappable)",
"child.editor.recurring_when_completed": "Recurring Chores — When Completed",
"child.editor.select_child": "Select a child...",
"child.editor.show_countdown": "Show reset countdown beside \"Today's Chores\"",
@@ -427,6 +434,7 @@
"overview.pending_count": "{count} pending",
"panel.activity_approve": "Approve",
"panel.activity_approve_all": "Approve all",
"panel.activity_approve_all_busy": "Approving…",
"panel.activity_approve_all_confirm": "Approve {count} pending chores?",
"panel.activity_approve_all_done": "Approved {count} chores",
"panel.activity_audit_log": "Audit log",
@@ -1738,5 +1746,6 @@
"panel.chore_image_bad_type": "Choose a JPEG, PNG or WebP image.",
"child.editor.pre_reader": "Picture-only mode (for young children)",
"child.editor.pre_reader_labels": "Show names in picture mode",
"child.editor.pre_reader_points": "Show point values instead of stars in picture mode",
"common.design.accessible": "Accessible"
}
+10 -1
View File
@@ -69,6 +69,7 @@
"approvals.all_caught_up": "Tout est à jour !",
"approvals.approve": "Approuver",
"approvals.approve_all": "Tout approuver",
"approvals.approve_all_busy": "Approbation…",
"approvals.approve_all_confirm": "Approuver {count} tâches en attente ?",
"approvals.card_description": "Une carte pour gérer les approbations de tâches en attente pour TaskMate",
"approvals.card_name": "Approbations TaskMate",
@@ -229,6 +230,12 @@
"child.editor.child": "Enfant",
"child.editor.child_helper": "Pour quel enfant est cette carte ?",
"child.editor.chores_not_due_today": "Tâches non prévues aujourd'hui",
"child.editor.dependency_mode": "Tâches en attente d'une autre",
"child.editor.dependency_hide": "Masquer — ne pas afficher avant déblocage",
"child.editor.dependency_dim": "Estomper — afficher en gris, non interactif",
"child.editor.dependency_show": "Afficher — affichage normal (toujours non cliquable)",
"child.editor.dependency_helper": "Ce qu'il faut afficher quand une tâche dépend d'une autre non encore approuvée aujourd'hui",
"child.blocked_by_dependency": "Fais d'abord {chores}",
"child.editor.due_days_dim": "Estomper — afficher en gris, non interactif",
"child.editor.due_days_helper": "S'applique aux tâches avec des jours de passage définis quand aujourd'hui n'est pas programmé",
"child.editor.due_days_hide": "Masquer — ne pas afficher",
@@ -241,7 +248,7 @@
"child.editor.recurrence_dim": "Estomper — afficher en gris, non interactif",
"child.editor.recurrence_helper": "Que montrer lorsqu'une tâche récurrente a été accomplie et que la fenêtre n'a pas encore été réinitialisée",
"child.editor.recurrence_hide": "Masquer — ne pas afficher avant d'être à nouveau disponible",
"child.editor.recurrence_show": "Afficher — afficher normalement malgré tout",
"child.editor.recurrence_show": "Afficher — affichage normal (toujours non cliquable)",
"child.editor.recurring_when_completed": "Tâches récurrentes — une fois terminées",
"child.editor.select_child": "Sélectionner un enfant...",
"child.editor.show_countdown": "Afficher le compte à rebours de réinitialisation à côté de \"Tâches d'aujourd'hui\"",
@@ -420,6 +427,7 @@
"overview.pending_count": "{count} en attente",
"panel.activity_approve": "Approuver",
"panel.activity_approve_all": "Tout approuver",
"panel.activity_approve_all_busy": "Approbation…",
"panel.activity_approve_all_confirm": "Approuver {count} tâches en attente ?",
"panel.activity_approve_all_done": "{count} tâches approuvées",
"panel.activity_audit_log": "Journal d'audit",
@@ -1738,5 +1746,6 @@
"panel.chore_image_bad_type": "Choisissez une image JPEG, PNG ou WebP.",
"child.editor.pre_reader": "Mode images seules (jeunes enfants)",
"child.editor.pre_reader_labels": "Afficher les noms en mode images",
"child.editor.pre_reader_points": "Afficher les points au lieu des étoiles en mode images",
"common.design.accessible": "Accessible"
}
+10 -1
View File
@@ -69,6 +69,7 @@
"approvals.all_caught_up": "Alt er oppdatert!",
"approvals.approve": "Godkjenn",
"approvals.approve_all": "Godkjenn alle",
"approvals.approve_all_busy": "Godkjenner…",
"approvals.approve_all_confirm": "Godkjenne {count} ventende gjøremål?",
"approvals.card_description": "Et kort for å håndtere ventende oppgavegodkjenninger",
"approvals.card_name": "TaskMate Godkjenninger",
@@ -229,6 +230,12 @@
"child.editor.child": "Barn",
"child.editor.child_helper": "Hvilket barn er dette kortet for?",
"child.editor.chores_not_due_today": "Oppgaver som ikke forfaller i dag",
"child.editor.dependency_mode": "Oppgaver som venter på en annen",
"child.editor.dependency_hide": "Skjul — ikke vis før den er låst opp",
"child.editor.dependency_dim": "Dim — vis gråtonet, ikke-interaktiv",
"child.editor.dependency_show": "Vis — vis normalt (fortsatt ikke trykkbar)",
"child.editor.dependency_helper": "Hva som vises når en oppgave avhenger av en annen som ikke er godkjent i dag ennå",
"child.blocked_by_dependency": "Gjør {chores} først",
"child.editor.due_days_dim": "Dim — vis gråtonet, ikke-interaktiv",
"child.editor.due_days_helper": "Gjelder oppgaver med satte dager når i dag ikke er planlagt",
"child.editor.due_days_hide": "Skjul — ikke vis",
@@ -241,7 +248,7 @@
"child.editor.recurrence_dim": "Dim — vis gråtonet, ikke-interaktiv",
"child.editor.recurrence_helper": "Hva som vises når en gjentagende oppgave er fullført og vinduet ikke har nullstilt seg ennå",
"child.editor.recurrence_hide": "Skjul — ikke vis før tilgjengelig igjen",
"child.editor.recurrence_show": "Vis — vis normalt uansett",
"child.editor.recurrence_show": "Vis — vis normalt (fortsatt ikke trykkbar)",
"child.editor.recurring_when_completed": "Gjentagende oppgaver — når fullført",
"child.editor.select_child": "Velg et barn...",
"child.editor.show_countdown": "Vis nedtelling ved siden av «Dagens oppgaver»",
@@ -420,6 +427,7 @@
"overview.pending_count": "{count} venter",
"panel.activity_approve": "Godkjenn",
"panel.activity_approve_all": "Godkjenn alle",
"panel.activity_approve_all_busy": "Godkjenner…",
"panel.activity_approve_all_confirm": "Godkjenne {count} ventende gjøremål?",
"panel.activity_approve_all_done": "Godkjente {count} gjøremål",
"panel.activity_audit_log": "Revisjonslogg",
@@ -1738,5 +1746,6 @@
"panel.chore_image_bad_type": "Velg et JPEG-, PNG- eller WebP-bilde.",
"child.editor.pre_reader": "Kun bilder (for små barn)",
"child.editor.pre_reader_labels": "Vis navn i bildemodus",
"child.editor.pre_reader_points": "Vis poengverdier i stedet for stjerner i bildemodus",
"common.design.accessible": "Tilgjengelig"
}
+10 -1
View File
@@ -69,6 +69,7 @@
"approvals.all_caught_up": "Alt er oppdatert!",
"approvals.approve": "Godkjenn",
"approvals.approve_all": "Godkjenn alle",
"approvals.approve_all_busy": "Godkjenner…",
"approvals.approve_all_confirm": "Godkjenne {count} ventande gjeremål?",
"approvals.card_description": "Eit kort for å handtere ventande oppgåvegodkjenningar",
"approvals.card_name": "TaskMate Godkjenningar",
@@ -229,6 +230,12 @@
"child.editor.child": "Born",
"child.editor.child_helper": "Kva born er dette kortet for?",
"child.editor.chores_not_due_today": "Oppgåver som ikkje forfell i dag",
"child.editor.dependency_mode": "Oppgåver som ventar på ei anna",
"child.editor.dependency_hide": "Skjul — ikkje vis før den er låst opp",
"child.editor.dependency_dim": "Dim — vis gråtona, ikkje-interaktiv",
"child.editor.dependency_show": "Vis — vis normalt (framleis ikkje trykkbar)",
"child.editor.dependency_helper": "Kva som blir vist når ei oppgåve avheng av ei anna som ikkje er godkjend i dag enno",
"child.blocked_by_dependency": "Gjer {chores} først",
"child.editor.due_days_dim": "Dim — vis gråtona, ikkje-interaktiv",
"child.editor.due_days_helper": "Gjeld oppgåver med sette dagar når i dag ikkje er planlagt",
"child.editor.due_days_hide": "Gøym — ikkje vis",
@@ -241,7 +248,7 @@
"child.editor.recurrence_dim": "Dim — vis gråtona, ikkje-interaktiv",
"child.editor.recurrence_helper": "Kva som visast når ei gjentakande oppgåve er fullført og vindauget ikkje har nullstilt seg enno",
"child.editor.recurrence_hide": "Gøym — ikkje vis før tilgjengeleg att",
"child.editor.recurrence_show": "Vis — vis normalt uansett",
"child.editor.recurrence_show": "Vis — vis normalt (framleis ikkje trykkbar)",
"child.editor.recurring_when_completed": "Gjentakande oppgåver — når fullført",
"child.editor.select_child": "Vel eit born...",
"child.editor.show_countdown": "Vis nedteljing ved sida av «Dagens oppgåver»",
@@ -420,6 +427,7 @@
"overview.pending_count": "{count} ventar",
"panel.activity_approve": "Godkjenn",
"panel.activity_approve_all": "Godkjenn alle",
"panel.activity_approve_all_busy": "Godkjenner…",
"panel.activity_approve_all_confirm": "Godkjenne {count} ventande gjeremål?",
"panel.activity_approve_all_done": "Godkjende {count} gjeremål",
"panel.activity_audit_log": "Revisjonslogg",
@@ -1738,5 +1746,6 @@
"panel.chore_image_bad_type": "Vel eit JPEG-, PNG- eller WebP-bilete.",
"child.editor.pre_reader": "Berre bilete (for små born)",
"child.editor.pre_reader_labels": "Vis namn i biletemodus",
"child.editor.pre_reader_points": "Vis poengverdiar i staden for stjerner i biletemodus",
"common.design.accessible": "Tilgjengeleg"
}
@@ -69,6 +69,7 @@
"approvals.all_caught_up": "Está tudo em dia!",
"approvals.approve": "Aprovar",
"approvals.approve_all": "Aprovar tudo",
"approvals.approve_all_busy": "Aprovando…",
"approvals.approve_all_confirm": "Aprovar {count} tarefas pendentes?",
"approvals.card_description": "Cartão para gerir aprovações de tarefas pendentes no TaskMate",
"approvals.card_name": "Aprovações TaskMate",
@@ -229,6 +230,12 @@
"child.editor.child": "Criança",
"child.editor.child_helper": "Para qual criança é este cartão?",
"child.editor.chores_not_due_today": "Tarefas Sem Prazo Hoje",
"child.editor.dependency_mode": "Tarefas aguardando outra",
"child.editor.dependency_hide": "Ocultar — não mostrar até desbloquear",
"child.editor.dependency_dim": "Atenuar — mostrar em cinzento, sem interação",
"child.editor.dependency_show": "Mostrar — mostrar normalmente (continua sem toque)",
"child.editor.dependency_helper": "O que mostrar quando uma tarefa depende de outra ainda não aprovada hoje",
"child.blocked_by_dependency": "Faça primeiro {chores}",
"child.editor.due_days_dim": "Atenuar — mostrar em cinzento, sem interação",
"child.editor.due_days_helper": "Aplica-se a tarefas com due_days definido quando hoje não está agendado",
"child.editor.due_days_hide": "Ocultar — não mostrar",
@@ -241,7 +248,7 @@
"child.editor.recurrence_dim": "Atenuar — mostrar em cinzento, sem interação",
"child.editor.recurrence_helper": "O que mostrar quando uma tarefa recorrente foi concluída e a janela ainda não reiniciou",
"child.editor.recurrence_hide": "Ocultar — não mostrar até estar disponível novamente",
"child.editor.recurrence_show": "Mostrar — mostrar normalmente",
"child.editor.recurrence_show": "Mostrar — mostrar normalmente (continua sem toque)",
"child.editor.recurring_when_completed": "Tarefas Recorrentes — Quando Concluídas",
"child.editor.select_child": "Selecionar uma criança...",
"child.editor.show_countdown": "Mostrar contagem regressiva de reinício junto a \"Tarefas de Hoje\"",
@@ -420,6 +427,7 @@
"overview.pending_count": "{count} pendente",
"panel.activity_approve": "Aprovar",
"panel.activity_approve_all": "Aprovar tudo",
"panel.activity_approve_all_busy": "Aprovando…",
"panel.activity_approve_all_confirm": "Aprovar {count} tarefas pendentes?",
"panel.activity_approve_all_done": "{count} tarefas aprovadas",
"panel.activity_audit_log": "Registro de auditoria",
@@ -1738,5 +1746,6 @@
"panel.chore_image_bad_type": "Escolha uma imagem JPEG, PNG ou WebP.",
"child.editor.pre_reader": "Modo só imagens (crianças pequenas)",
"child.editor.pre_reader_labels": "Mostrar nomes no modo imagens",
"child.editor.pre_reader_points": "Mostrar pontos em vez de estrelas no modo imagens",
"common.design.accessible": "Acessível"
}
+10 -1
View File
@@ -69,6 +69,7 @@
"approvals.all_caught_up": "Está tudo em dia!",
"approvals.approve": "Aprovar",
"approvals.approve_all": "Aprovar tudo",
"approvals.approve_all_busy": "A aprovar…",
"approvals.approve_all_confirm": "Aprovar {count} tarefas pendentes?",
"approvals.card_description": "Cartão para gerir aprovações de tarefas pendentes no TaskMate",
"approvals.card_name": "Aprovações TaskMate",
@@ -229,6 +230,12 @@
"child.editor.child": "Criança",
"child.editor.child_helper": "Para qual criança é este cartão?",
"child.editor.chores_not_due_today": "Tarefas Sem Prazo Hoje",
"child.editor.dependency_mode": "Tarefas à espera de outra",
"child.editor.dependency_hide": "Ocultar — não mostrar até desbloquear",
"child.editor.dependency_dim": "Atenuar — mostrar em cinzento, sem interação",
"child.editor.dependency_show": "Mostrar — mostrar normalmente (continua sem toque)",
"child.editor.dependency_helper": "O que mostrar quando uma tarefa depende de outra ainda não aprovada hoje",
"child.blocked_by_dependency": "Faz primeiro {chores}",
"child.editor.due_days_dim": "Atenuar — mostrar em cinzento, sem interação",
"child.editor.due_days_helper": "Aplica-se a tarefas com due_days definido quando hoje não está agendado",
"child.editor.due_days_hide": "Ocultar — não mostrar",
@@ -241,7 +248,7 @@
"child.editor.recurrence_dim": "Atenuar — mostrar em cinzento, sem interação",
"child.editor.recurrence_helper": "O que mostrar quando uma tarefa recorrente foi concluída e a janela ainda não reiniciou",
"child.editor.recurrence_hide": "Ocultar — não mostrar até estar disponível novamente",
"child.editor.recurrence_show": "Mostrar — mostrar normalmente",
"child.editor.recurrence_show": "Mostrar — mostrar normalmente (continua sem toque)",
"child.editor.recurring_when_completed": "Tarefas Recorrentes — Quando Concluídas",
"child.editor.select_child": "Selecionar uma criança...",
"child.editor.show_countdown": "Mostrar contagem regressiva de reinício junto a \"Tarefas de Hoje\"",
@@ -425,6 +432,7 @@
"overview.pending_count": "{count} pendente",
"panel.activity_approve": "Aprovar",
"panel.activity_approve_all": "Aprovar tudo",
"panel.activity_approve_all_busy": "A aprovar…",
"panel.activity_approve_all_confirm": "Aprovar {count} tarefas pendentes?",
"panel.activity_approve_all_done": "{count} tarefas aprovadas",
"panel.activity_audit_log": "Registo de auditoria",
@@ -1738,5 +1746,6 @@
"panel.chore_image_bad_type": "Escolha uma imagem JPEG, PNG ou WebP.",
"child.editor.pre_reader": "Modo só imagens (crianças pequenas)",
"child.editor.pre_reader_labels": "Mostrar nomes no modo imagens",
"child.editor.pre_reader_points": "Mostrar pontos em vez de estrelas no modo imagens",
"common.design.accessible": "Acessível"
}
@@ -106,6 +106,9 @@ class TaskMateApprovalsCard extends LitElement {
}
.approve-all-btn {
display: inline-flex;
align-items: center;
gap: 6px;
background: rgba(255, 255, 255, 0.2);
color: white;
border: 1px solid rgba(255, 255, 255, 0.45);
@@ -118,6 +121,18 @@ class TaskMateApprovalsCard extends LitElement {
}
.approve-all-btn:hover { background: rgba(255, 255, 255, 0.3); }
.approve-all-btn:disabled { opacity: 0.6; cursor: default; }
/* Reuses the spin keyframes already defined for the loading spinner. */
.btn-spinner {
width: 12px; height: 12px;
border: 2px solid rgba(255, 255, 255, 0.45);
border-top-color: #fff;
border-radius: 50%;
animation: spin 0.7s linear infinite;
flex-shrink: 0;
}
@media (prefers-reduced-motion: reduce) {
.btn-spinner { animation: none; opacity: 0.7; }
}
.card-content { padding: 16px; }
@@ -537,7 +552,9 @@ class TaskMateApprovalsCard extends LitElement {
<div class="header-right">
${filteredCompletions.length > 0
? html`<button class="approve-all-btn" ?disabled="${this._loading['__all__']}"
@click="${() => this._handleApproveAll(filteredCompletions)}">${this._t('approvals.approve_all')}</button>`
@click="${() => this._handleApproveAll(filteredCompletions)}">${this._loading['__all__']
? html`<span class="btn-spinner"></span>${this._t('approvals.approve_all_busy')}`
: this._t('approvals.approve_all')}</button>`
: ""}
${totalPending > 0 ? html`<span class="pending-count">${totalPending}</span>` : ""}
</div>
@@ -724,7 +741,9 @@ class TaskMateApprovalsCard extends LitElement {
<span class="tt">${title}</span>
${completions.length > 0
? html`<button class="approve-all-btn" ?disabled="${this._loading['__all__']}"
@click="${() => this._handleApproveAll(completions)}">${this._t('approvals.approve_all')}</button>`
@click="${() => this._handleApproveAll(completions)}">${this._loading['__all__']
? html`<span class="btn-spinner"></span>${this._t('approvals.approve_all_busy')}`
: this._t('approvals.approve_all')}</button>`
: ""}
${count > 0 ? html`<span class="cnt">${count}</span>` : ""}
</div>`;
@@ -785,7 +804,7 @@ class TaskMateApprovalsCard extends LitElement {
<button class="btn bad" style="flex:1" ?disabled="${isLoading}" @click="${reject}">👎 ${this._t('approvals.reject')}</button>`;
}
_apPhotoDesigned(it, cls, fallback) {
_apPhotoDesigned(it, cls) {
const photoUrl = tmSafePhotoUrl(it.photo);
if (it.kind !== "completion" || !photoUrl) {
return it.kind === "claim" && it.icon
@@ -1154,8 +1173,6 @@ class TaskMateApprovalsCard extends LitElement {
}
_getDayLabel(date) {
const timezone = this._getTimezone();
// Get today's date parts in HA timezone
const now = new Date();
const todayParts = this._getDatePartsInTimezone(now);
@@ -1252,7 +1269,7 @@ class TaskMateApprovalsCard extends LitElement {
_renderApprovals(groupedByDay) {
return html`
${groupedByDay.map(
([dayKey, dayGroup]) => html`
([, dayGroup]) => html`
<div class="day-group">
<div class="day-header">${dayGroup.label}</div>
${this._renderTimeCategories(dayGroup.timeCategories)}
@@ -30,7 +30,11 @@
// Full pending-approvals lists (chore_completions / reward_claims). These
// are NOT filtered to today, so approval cards pointed at the overview
// entity can still surface a completion left pending from a previous day.
"sensor.pending_approvals",
// Named "Pending Approvals", but it carries no _attr_has_entity_name, so
// the TaskMate device name still prefixes its entity id. This entry was
// missing that prefix and so matched nothing at all, silently dropping
// chore_completions and mandatory_misses from every merge (#798).
"sensor.taskmate_pending_approvals",
];
function mergedAttributes(hass, primaryEntityId) {
@@ -697,7 +697,7 @@ class TaskMateCalendarCard extends LitElement {
style="${dim ? "" : `color:var(--tmd-${tok});background:color-mix(in srgb,var(--tmd-${tok}) 16%,transparent);border-color:transparent`}">${glyph[item.state] || glyph.due} ${item.chore.name}</span>`;
}
_calPlayroom(blocks, pointsIcon) {
_calPlayroom(blocks, _pointsIcon) {
const glyph = { approved: "✓", pending: "◌", due: "—" };
return html`
<div class="grid cal-grid">
@@ -713,7 +713,7 @@ class TaskMateCalendarCard extends LitElement {
</div>`;
}
_calConsole(blocks, pointsIcon) {
_calConsole(blocks, _pointsIcon) {
const glyph = { approved: "▮", pending: "▯", due: "▢" };
return html`
<div class="grid cal-grid-cn">
@@ -733,7 +733,7 @@ class TaskMateCalendarCard extends LitElement {
</div>`;
}
_calCleanpro(blocks, pointsIcon) {
_calCleanpro(blocks, _pointsIcon) {
const glyph = { approved: "●", pending: "○", due: "—" };
return html`
<div class="grid cal-grid-cp">
@@ -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">&#9654;</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">&#9654;</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,
};
@@ -276,7 +276,6 @@ class TaskMateGraphCard extends LitElement {
const tz = this.hass?.config?.time_zone || Intl.DateTimeFormat().resolvedOptions().timeZone;
const attrs = (window.__taskmate_attrs && window.__taskmate_attrs(this.hass, this.config.entity)) || entity.attributes || {};
let children = attrs.children || [];
const pointsIcon = attrs.points_icon || "mdi:star";
const pointsName = attrs.points_name || this._t('common.points');
const days = Math.max(3, Math.min(90, this.config.days || 14));
@@ -919,7 +919,6 @@ export function createIncentiveCard(P) {
_renderDesignedRow(b, design) {
const isEditing = this._editingBonus?.id === b.id;
const tone = this._designTone(0);
const accent = P.designAccent;
const pv = P.dpts(b.points);
@@ -348,7 +348,7 @@ class TaskMateLeaderboardCard extends LitElement {
// Solo mode
if (children.length === 1) {
return this._renderSolo(sorted[0], weeklyPoints, pointsIcon, pointsName, periodLabel);
return this._renderSolo(sorted[0], weeklyPoints, pointsIcon, pointsName);
}
return html`
@@ -464,8 +464,7 @@ class TaskMateLeaderboardCard extends LitElement {
`;
}
_renderSolo(child, weeklyPoints, pointsIcon, pointsName, periodLabel) {
const entity = this.hass.states[this.config.entity];
_renderSolo(child, weeklyPoints, pointsIcon, pointsName) {
const totalChores = child.total_chores_completed || 0;
const bestStreak = child.best_streak || 0;
const weekly = weeklyPoints[child.id] || 0;
@@ -631,7 +631,7 @@ class TaskMateOverviewCard extends LitElement {
</div>`;
}
_ovKid(design, k, isParent, pointsIcon) {
_ovKid(design, k, isParent, _pointsIcon) {
const cls = design === "console" ? "cn" : design === "cleanpro" ? "cp" : "";
const nameUpper = design === "console" ? `${k.child.name.toUpperCase()} · ${k.pct}%` : k.child.name;
const isOpen = !!this._expanded[k.child.id];
@@ -652,7 +652,7 @@ class TaskMateOverviewCard extends LitElement {
}
/** Admin "complete on behalf" panel — same handler as the classic tile. */
_ovBehalf(k, pointsIcon) {
_ovBehalf(k, _pointsIcon) {
return html`
<div class="ov-behalf" @click="${(e) => e.stopPropagation()}">
<div class="ov-behalf-hdr">${this._t('common.complete_on_behalf_heading')} · ${k.child.name}</div>
@@ -693,7 +693,7 @@ class TaskMateOverviewCard extends LitElement {
</div>`;
}
_renderChildTile(child, chores, completions, pointsIcon, pointsName) {
_renderChildTile(child, chores, completions, pointsIcon, _pointsName) {
// Avatar now included directly in children array from the overview sensor
const avatar = child.avatar || "mdi:account-circle";
@@ -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);
@@ -755,7 +755,7 @@ class TaskMateParentDashboardCard extends LitElement {
_dEmpty(msg) { return html`<div class="tmd-empty">${msg}</div>`; }
_dOverview(design, children, chores, completions, attrs, pointsIcon) {
_dOverview(design, children, chores, completions, attrs, _pointsIcon) {
if (!children.length) return this._dEmpty(this._t('dashboard.empty_no_children'));
return html`${children.map((child, i) => {
const tone = this._designTone(i);
@@ -805,7 +805,7 @@ class TaskMateParentDashboardCard extends LitElement {
})}`;
}
_dApprovals(design, pending, children, chores, pointsIcon) {
_dApprovals(design, pending, children, chores, _pointsIcon) {
if (!pending.length) return this._dEmpty(this._t('dashboard.empty_approvals'));
const childMap = {}; children.forEach(c => { childMap[c.id] = c; });
const choreMap = {}; chores.forEach(c => { choreMap[c.id] = c; });
@@ -841,7 +841,7 @@ class TaskMateParentDashboardCard extends LitElement {
})}`;
}
_dClaims(design, claims, pointsIcon) {
_dClaims(design, claims, _pointsIcon) {
if (!claims.length) return this._dEmpty(this._t('dashboard.empty_claims'));
return html`${claims.map((claim, i) => {
const tone = this._designTone(i);
@@ -924,7 +924,7 @@ class TaskMateParentDashboardCard extends LitElement {
})}`;
}
_renderOverview(children, chores, completions, pointsIcon, pointsName) {
_renderOverview(children, chores, completions, pointsIcon, _pointsName) {
if (!children.length) return html`<div class="empty-section"><ha-icon icon="mdi:account-group"></ha-icon><span>${this._t('dashboard.empty_no_children')}</span></div>`;
const attrs = (window.__taskmate_attrs && window.__taskmate_attrs(this.hass, this.config?.entity)) || {};
@@ -984,7 +984,7 @@ class TaskMatePointsCard extends LitElement {
}
_renderDialog() {
const { child, action, pointsIcon, pointsName } = this._dialog;
const { child, action, pointsName } = this._dialog;
const isAdd = action === "add";
const isLoading = this._loading[`dialog_${child.id}`];
@@ -856,7 +856,6 @@ class TaskMateReorderCard extends LitElement {
const chores = attrs.chores || [];
const childChores = this._getChoresForChild(chores, child.id);
const pointsIcon = attrs.points_icon || "mdi:star";
const header = html`
<div class="tmd-hd" style="--ac:var(--tmd-c1)">
@@ -913,7 +912,7 @@ class TaskMateReorderCard extends LitElement {
</div>
<div class="d-list">
${allCategoryChores.map((chore, index) =>
this._renderDesignedChoreItem(chore, index, allCategoryChores.length, category, pointsIcon)
this._renderDesignedChoreItem(chore, index, allCategoryChores.length, category)
)}
</div>`;
})}
@@ -922,7 +921,7 @@ class TaskMateReorderCard extends LitElement {
`;
}
_renderDesignedChoreItem(chore, index, total, category, pointsIcon) {
_renderDesignedChoreItem(chore, index, total, category) {
const isFirst = index === 0;
const isLast = index === total - 1;
const v = window.__taskmate_chore_visual(chore);
@@ -699,7 +699,7 @@ class TaskMateRewardProgressCard extends LitElement {
_renderDesigned(design) {
const hd = _safeColor(this.config.header_color, '#00897b');
const wrap = (body, icon) => html`
const wrap = (body) => html`
<ha-card class="tmd" style="--hd:${hd}">
<div class="tmd-hd">
<span class="ic">🎯</span>
@@ -1147,8 +1147,8 @@ class TaskMateRewardsCard extends LitElement {
if (a.is_jackpot && !b.is_jackpot) return -1;
if (!a.is_jackpot && b.is_jackpot) return 1;
// Within same type, sort by calculated cost ascending
const aCost = this._getDisplayCost(a, children);
const bCost = this._getDisplayCost(b, children);
const aCost = this._getDisplayCost(a);
const bCost = this._getDisplayCost(b);
return aCost - bCost;
});
@@ -1304,7 +1304,7 @@ class TaskMateRewardsCard extends LitElement {
// pool, so jackpots never show the single-child claim button.
const enablePoolMode = cardForcesPool || reward.pool_enabled === true || isJackpot;
// All costs are static — just use reward.cost
const displayCost = this._getDisplayCost(reward, children);
const displayCost = this._getDisplayCost(reward);
// Get relevant children for this reward
const relevantChildren = assignedTo.length === 0
@@ -1329,7 +1329,6 @@ class TaskMateRewardsCard extends LitElement {
const sharePerChild = relevantChildren.length > 0
? Math.round(displayCost / relevantChildren.length)
: displayCost;
relevantChildren.forEach((child, index) => {
// In pool mode, show allocated points only; in wallet mode show wallet points
const points = enablePoolMode
@@ -1703,7 +1702,7 @@ class TaskMateRewardsCard extends LitElement {
* For non-jackpot rewards, each child has their own calculated cost.
* For jackpot rewards, all children share the same cost.
*/
_getDisplayCost(reward, children) {
_getDisplayCost(reward) {
// All reward costs are static — just return reward.cost
// calculated_costs is still provided by the sensor for jackpot display purposes
const calculatedCosts = reward.calculated_costs || {};
@@ -1740,9 +1739,6 @@ class TaskMateRewardsCard extends LitElement {
};
});
// Calculate total percentage for display
const totalPercentage = cost > 0 ? Math.min((totalStars / cost) * 100, 100) : 0;
return html`
<div class="progress-section">
<div class="progress-bar-container">
@@ -1803,7 +1799,7 @@ class TaskMateRewardsCard extends LitElement {
// shared jar and it's redeemed when full. Combined points only work via the
// pool, so jackpots never show the single-child claim button.
const enablePoolMode = cardForcesPool || reward.pool_enabled === true || isJackpot;
const displayCost = this._getDisplayCost(reward, children);
const displayCost = this._getDisplayCost(reward);
const relevantChildren = assignedTo.length === 0
? children
@@ -1821,9 +1817,6 @@ class TaskMateRewardsCard extends LitElement {
const childContributions = [];
if (isJackpot) {
const sharePerChild = relevantChildren.length > 0
? Math.round(displayCost / relevantChildren.length)
: displayCost;
relevantChildren.forEach((child, index) => {
const points = enablePoolMode
? (poolAllocations[child.id] || 0)
@@ -1906,7 +1899,7 @@ class TaskMateRewardsCard extends LitElement {
if (!entity) {
return html`<ha-card class="tmd" style="--hd:${hd}">
${this._designHeader(0, hd)}
${this._designHeader(0)}
<div class="tmd-bd"><div class="tmd-empty">${this._t('common.entity_not_found', { entity: this.config.entity })}</div></div>
</ha-card>`;
}
@@ -1928,7 +1921,7 @@ class TaskMateRewardsCard extends LitElement {
const sortedRewards = [...rewards].sort((a, b) => {
if (a.is_jackpot && !b.is_jackpot) return -1;
if (!a.is_jackpot && b.is_jackpot) return 1;
return this._getDisplayCost(a, children) - this._getDisplayCost(b, children);
return this._getDisplayCost(a) - this._getDisplayCost(b);
});
const cardForcesPool = this.config.enable_pool_mode === true;
@@ -1936,7 +1929,7 @@ class TaskMateRewardsCard extends LitElement {
const activeChild = activeChildId ? children.find((c) => c.id === activeChildId) : null;
const anyPoolReward = sortedRewards.some((r) => cardForcesPool || r.pool_enabled === true || r.is_jackpot);
const header = this._designHeader(rewards.length, hd);
const header = this._designHeader(rewards.length);
const body = sortedRewards.length === 0
? html`<div class="tmd-empty">${this._t('rewards.empty_title')}<br>${this._t('rewards.empty_subtitle')}</div>`
@@ -1958,7 +1951,7 @@ class TaskMateRewardsCard extends LitElement {
`;
}
_designHeader(count, hd) {
_designHeader(count) {
return html`
<div class="tmd-hd">
<span class="ic">🎁</span>
@@ -1969,7 +1962,7 @@ class TaskMateRewardsCard extends LitElement {
</div>`;
}
_designChildSelector(child, pointsIcon, pointsName, design) {
_designChildSelector(child, _pointsIcon, _pointsName, _design) {
const spendable = typeof child.spendable_balance === 'number'
? child.spendable_balance : (child.points || 0);
return html`
@@ -1983,7 +1976,7 @@ class TaskMateRewardsCard extends LitElement {
</div>`;
}
_designRewardRow(reward, children, pointsIcon, pointsName, design) {
_designRewardRow(reward, children, pointsIcon, pointsName, _design) {
const d = this._rewardData(reward, children);
const tone = this._designTone((reward.id || reward.name || '').length);
const isLoading = this._loading[reward.id];
@@ -322,7 +322,7 @@ class TaskMateStreakCard extends LitElement {
// Use backend-calculated streak if available, fall back to client calculation
const streak = child.current_streak !== undefined
? child.current_streak
: this._calculateStreak(childCompletions, chores, child.id);
: this._calculateStreak(childCompletions);
const daysShown = this.config.streak_days_shown || 14;
const dayDots = this._buildDayDots(childCompletions, chores, child.id, daysShown);
const achievements = this._getAchievements(child, childCompletions, streak, entity_ref);
@@ -370,7 +370,7 @@ class TaskMateStreakCard extends LitElement {
`;
}
_calculateStreak(completions, chores, childId) {
_calculateStreak(completions) {
const tz = this.hass?.config?.time_zone || Intl.DateTimeFormat().resolvedOptions().timeZone;
// Build set of unique days with at least one completion
@@ -492,7 +492,7 @@ class TaskMateStreakCard extends LitElement {
const childCompletions = completions.filter(c => c.child_id === child.id);
const streak = child.current_streak !== undefined
? child.current_streak
: this._calculateStreak(childCompletions, chores, child.id);
: this._calculateStreak(childCompletions);
const daysShown = this.config.streak_days_shown || 14;
const dayDots = this._buildDayDots(childCompletions, chores, child.id, daysShown);
const achievements = this._getAchievements(child, childCompletions, streak, entity_ref);
@@ -549,10 +549,6 @@ class TaskMateStreakCard extends LitElement {
return html`<ha-card class="tmd" style="--hd:${hd}">${header}<div class="tmd-bd">${body}</div></ha-card>`;
}
_earnedBadges(achievements) {
return achievements.filter(a => a.earned);
}
// Classic shows earned badges PLUS the next locked milestone. _getAchievements
// already returns exactly that set, so the designed badge rows render it whole,
// dimming the locked entry to match the classic .badge.locked treatment.
@@ -401,7 +401,7 @@ class TaskMateWeeklyCard extends LitElement {
// Max completions in a day for chart scale
const maxPerDay = Math.max(1, ...weekDays.map(d => (approvedCompletionsByDay[d.key] || []).length));
const weekLabel = this._getWeekLabel(weekDays, tz);
const weekLabel = this._getWeekLabel(weekDays);
return html`
<ha-card>
@@ -596,7 +596,7 @@ class TaskMateWeeklyCard extends LitElement {
}
const d = this._weekData(entity, tz);
const weekLabel = this._getWeekLabel(d.weekDays, tz);
const weekLabel = this._getWeekLabel(d.weekDays);
const title = this.config.title || this._t("weekly.default_title");
const icon = design === "playroom" ? "📅" : design === "console" ? "▦" : "▤";
@@ -718,7 +718,7 @@ class TaskMateWeeklyCard extends LitElement {
return days;
}
_getWeekLabel(weekDays, tz) {
_getWeekLabel(weekDays) {
const first = weekDays[0].date;
const last = weekDays[6].date;
const fmt = { month: "short", day: "numeric" };