59 files
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
"common.design.playroom": "Playroom",
|
||||
"common.design.console": "Console",
|
||||
"common.design.cleanpro": "Clean Pro",
|
||||
"common.design.graphite": "Graphit",
|
||||
"panel.settings_card_design_label": "Standard-Kartendesign",
|
||||
"panel.settings_card_design_hint": "Der Stil neuer Karten, sofern nicht pro Karte überschrieben.",
|
||||
"activity.a_chore": "eine lästige Pflicht",
|
||||
@@ -55,9 +56,11 @@
|
||||
"activity.reason_bonus": "Bonus: {name}",
|
||||
"activity.reason_penalty": "Strafe: {name}",
|
||||
"activity.reason_perfect_week": "Perfekter Wochenbonus!",
|
||||
"activity.reason_pool_refund_assignment_changed": "Poolrückerstattung (Zuweisung der Prämie geändert): {name}",
|
||||
"activity.reason_pool_refund_cost_reduced": "Pool-Rückerstattung (Prämienkosten reduziert): {name}",
|
||||
"activity.reason_pool_refund_deleted": "Poolrückerstattung (Prämie gelöscht): {name}",
|
||||
"activity.reason_pool_refund_expired": "Poolrückerstattung (Prämie abgelaufen): {name}",
|
||||
"activity.reason_pool_refund_funding_changed": "Poolrückerstattung (Finanzierung der Prämie geändert): {name}",
|
||||
"activity.reason_pool_refund_sold_out": "Poolrückerstattung (Prämie ausverkauft): {name}",
|
||||
"activity.reason_streak_milestone": "Serie-Meilensteinbonus ({days} Tage Serie!)",
|
||||
"activity.reason_weekend_bonus": "Wochenendbonus (×{multiplier})",
|
||||
@@ -158,6 +161,7 @@
|
||||
"badges.entity_not_found": "{entity} nicht gefunden",
|
||||
"badges.label": "Abzeichen",
|
||||
"badges.next_up": "Als Nächstes",
|
||||
"badges.one_more": "Nur noch eins – dann gehört es dir!",
|
||||
"badges.title_with_name": "Abzeichen von {name}",
|
||||
"bonuses.add_bonus": "Bonus hinzufügen",
|
||||
"bonuses.applying_to": "Beantragung bei {childName} – aktuelles Guthaben: {points} {pointsName}",
|
||||
@@ -229,6 +233,8 @@
|
||||
"child.extra_submit": "Senden",
|
||||
"child.extra_needs_desc": "Schreib zuerst, was du gemacht hast.",
|
||||
"child.open_ended_tag": "Beschreiben",
|
||||
"child.weekly_target_done": "Für diese Woche erledigt",
|
||||
"child.weekly_target_progress": "{done}/{target} diese Woche",
|
||||
"child.photo_take": "Foto aufnehmen",
|
||||
"child.photo_hint": "Zeig deine erledigte Aufgabe",
|
||||
"child.photo_retake": "Neu aufnehmen",
|
||||
@@ -616,6 +622,9 @@
|
||||
"panel.chore_daily_cap_label": "Tageslimit (Minuten, 0 = unbegrenzt)",
|
||||
"panel.chore_daily_limit_label": "Tageslimit",
|
||||
"panel.chore_days_label": "Wochentage (leer = jeden Tag)",
|
||||
"panel.chore_weekly_target_pill": "{count}×/Woche",
|
||||
"panel.chore_weekly_target_hint": "Wie oft pro Woche die Aufgabe erledigt werden muss, an beliebigen der gewählten Tage. Ist die Anzahl erreicht, wird sie nicht mehr angezeigt; montags beginnt die Zählung neu.",
|
||||
"panel.chore_weekly_target_label": "Wochenziel (0 = aus)",
|
||||
"panel.chore_description_label": "Beschreibung (optional)",
|
||||
"panel.chore_enabled_label": "Aktiviert",
|
||||
"panel.chore_first_occurrence_label": "Erstes Vorkommen",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"common.design.playroom": "Playroom",
|
||||
"common.design.console": "Console",
|
||||
"common.design.cleanpro": "Clean Pro",
|
||||
"common.design.graphite": "Graphite",
|
||||
"panel.settings_card_design_label": "Default card design",
|
||||
"panel.settings_card_design_hint": "The style new cards use unless overridden per card.",
|
||||
"activity.a_chore": "a chore",
|
||||
@@ -55,9 +56,11 @@
|
||||
"activity.reason_bonus": "Bonus: {name}",
|
||||
"activity.reason_penalty": "Penalty: {name}",
|
||||
"activity.reason_perfect_week": "Perfect week bonus!",
|
||||
"activity.reason_pool_refund_assignment_changed": "Pool refund (reward assignment changed): {name}",
|
||||
"activity.reason_pool_refund_cost_reduced": "Pool refund (reward cost reduced): {name}",
|
||||
"activity.reason_pool_refund_deleted": "Pool refund (reward deleted): {name}",
|
||||
"activity.reason_pool_refund_expired": "Pool refund (reward expired): {name}",
|
||||
"activity.reason_pool_refund_funding_changed": "Pool refund (reward funding changed): {name}",
|
||||
"activity.reason_pool_refund_sold_out": "Pool refund (reward sold out): {name}",
|
||||
"activity.reason_streak_milestone": "Streak milestone bonus ({days} day streak!)",
|
||||
"activity.reason_weekend_bonus": "Weekend bonus (×{multiplier})",
|
||||
@@ -159,6 +162,7 @@
|
||||
"badges.entity_not_found": "{entity} not found",
|
||||
"badges.label": "Badges",
|
||||
"badges.next_up": "Next up",
|
||||
"badges.one_more": "One more and it's yours!",
|
||||
"badges.title_with_name": "{name}'s Badges",
|
||||
"bonuses.add_bonus": "Add Bonus",
|
||||
"bonuses.applying_to": "Applying to {childName} — current balance: {points} {pointsName}",
|
||||
@@ -231,6 +235,8 @@
|
||||
"child.extra_submit": "Send",
|
||||
"child.extra_needs_desc": "Write what you did first.",
|
||||
"child.open_ended_tag": "Describe it",
|
||||
"child.weekly_target_done": "Done for this week",
|
||||
"child.weekly_target_progress": "{done}/{target} this week",
|
||||
"child.photo_take": "Take a photo",
|
||||
"child.photo_hint": "Show your finished chore",
|
||||
"child.photo_retake": "Retake",
|
||||
@@ -654,6 +660,9 @@
|
||||
"panel.chore_daily_cap_label": "Daily cap (minutes, 0 = unlimited)",
|
||||
"panel.chore_daily_limit_label": "Daily limit",
|
||||
"panel.chore_days_label": "Days of week (leave empty = every day)",
|
||||
"panel.chore_weekly_target_pill": "{count}×/week",
|
||||
"panel.chore_weekly_target_hint": "How many times a week this has to be done, on any of the chosen days. It stops being offered once the count is reached, and resets on Monday.",
|
||||
"panel.chore_weekly_target_label": "Weekly target (0 = off)",
|
||||
"panel.chore_description_label": "Description (optional)",
|
||||
"panel.chore_enabled_label": "Enabled",
|
||||
"panel.chore_first_occurrence_label": "First occurrence",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"common.design.playroom": "Playroom",
|
||||
"common.design.console": "Console",
|
||||
"common.design.cleanpro": "Clean Pro",
|
||||
"common.design.graphite": "Graphite",
|
||||
"panel.settings_card_design_label": "Default card design",
|
||||
"panel.settings_card_design_hint": "The style new cards use unless overridden per card.",
|
||||
"activity.a_chore": "a chore",
|
||||
@@ -55,9 +56,11 @@
|
||||
"activity.reason_bonus": "Bonus: {name}",
|
||||
"activity.reason_penalty": "Penalty: {name}",
|
||||
"activity.reason_perfect_week": "Perfect week bonus!",
|
||||
"activity.reason_pool_refund_assignment_changed": "Pool refund (reward assignment changed): {name}",
|
||||
"activity.reason_pool_refund_cost_reduced": "Pool refund (reward cost reduced): {name}",
|
||||
"activity.reason_pool_refund_deleted": "Pool refund (reward deleted): {name}",
|
||||
"activity.reason_pool_refund_expired": "Pool refund (reward expired): {name}",
|
||||
"activity.reason_pool_refund_funding_changed": "Pool refund (reward funding changed): {name}",
|
||||
"activity.reason_pool_refund_sold_out": "Pool refund (reward sold out): {name}",
|
||||
"activity.reason_streak_milestone": "Streak milestone bonus ({days} day streak!)",
|
||||
"activity.reason_weekend_bonus": "Weekend bonus (×{multiplier})",
|
||||
@@ -159,6 +162,7 @@
|
||||
"badges.entity_not_found": "{entity} not found",
|
||||
"badges.label": "Badges",
|
||||
"badges.next_up": "Next up",
|
||||
"badges.one_more": "One more and it's yours!",
|
||||
"badges.title_with_name": "{name}'s Badges",
|
||||
"bonuses.add_bonus": "Add Bonus",
|
||||
"bonuses.applying_to": "Applying to {childName} — current balance: {points} {pointsName}",
|
||||
@@ -231,6 +235,8 @@
|
||||
"child.extra_submit": "Send",
|
||||
"child.extra_needs_desc": "Write what you did first.",
|
||||
"child.open_ended_tag": "Describe it",
|
||||
"child.weekly_target_done": "Done for this week",
|
||||
"child.weekly_target_progress": "{done}/{target} this week",
|
||||
"child.photo_take": "Take a photo",
|
||||
"child.photo_hint": "Show your finished chore",
|
||||
"child.photo_retake": "Retake",
|
||||
@@ -654,6 +660,9 @@
|
||||
"panel.chore_daily_cap_label": "Daily cap (minutes, 0 = unlimited)",
|
||||
"panel.chore_daily_limit_label": "Daily limit",
|
||||
"panel.chore_days_label": "Days of week (leave empty = every day)",
|
||||
"panel.chore_weekly_target_pill": "{count}×/week",
|
||||
"panel.chore_weekly_target_hint": "How many times a week this has to be done, on any of the chosen days. It stops being offered once the count is reached, and resets on Monday.",
|
||||
"panel.chore_weekly_target_label": "Weekly target (0 = off)",
|
||||
"panel.chore_description_label": "Description (optional)",
|
||||
"panel.chore_enabled_label": "Enabled",
|
||||
"panel.chore_first_occurrence_label": "First occurrence",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"common.design.playroom": "Playroom",
|
||||
"common.design.console": "Console",
|
||||
"common.design.cleanpro": "Clean Pro",
|
||||
"common.design.graphite": "Graphite",
|
||||
"panel.settings_card_design_label": "Design de carte par défaut",
|
||||
"panel.settings_card_design_hint": "Le style des nouvelles cartes, sauf remplacement par carte.",
|
||||
"activity.a_chore": "une tâche",
|
||||
@@ -55,9 +56,11 @@
|
||||
"activity.reason_bonus": "Bonus : {name}",
|
||||
"activity.reason_penalty": "Pénalité : {name}",
|
||||
"activity.reason_perfect_week": "Bonus de semaine parfaite !",
|
||||
"activity.reason_pool_refund_assignment_changed": "Remboursement de cagnotte (attribution de la récompense modifiée) : {name}",
|
||||
"activity.reason_pool_refund_cost_reduced": "Remboursement de cagnotte (coût réduit) : {name}",
|
||||
"activity.reason_pool_refund_deleted": "Remboursement de cagnotte (récompense supprimée) : {name}",
|
||||
"activity.reason_pool_refund_expired": "Remboursement de cagnotte (récompense expirée) : {name}",
|
||||
"activity.reason_pool_refund_funding_changed": "Remboursement de cagnotte (financement de la récompense modifié) : {name}",
|
||||
"activity.reason_pool_refund_sold_out": "Remboursement de cagnotte (récompense épuisée) : {name}",
|
||||
"activity.reason_streak_milestone": "Bonus de palier de série ({days} jours de suite !)",
|
||||
"activity.reason_weekend_bonus": "Bonus de week-end (×{multiplier})",
|
||||
@@ -158,6 +161,7 @@
|
||||
"badges.entity_not_found": "{entity} introuvable",
|
||||
"badges.label": "Badges",
|
||||
"badges.next_up": "Prochain",
|
||||
"badges.one_more": "Encore un et il est à toi !",
|
||||
"badges.title_with_name": "Badges de {name}",
|
||||
"bonuses.add_bonus": "Ajouter un bonus",
|
||||
"bonuses.applying_to": "Application à {childName} — solde actuel : {points} {pointsName}",
|
||||
@@ -229,6 +233,8 @@
|
||||
"child.extra_submit": "Envoyer",
|
||||
"child.extra_needs_desc": "Écris d'abord ce que tu as fait.",
|
||||
"child.open_ended_tag": "À décrire",
|
||||
"child.weekly_target_done": "Terminé pour cette semaine",
|
||||
"child.weekly_target_progress": "{done}/{target} cette semaine",
|
||||
"child.photo_take": "Prendre une photo",
|
||||
"child.photo_hint": "Montre ta tâche terminée",
|
||||
"child.photo_retake": "Reprendre",
|
||||
@@ -616,6 +622,9 @@
|
||||
"panel.chore_daily_cap_label": "Plafond journalier (minutes, 0 = illimité)",
|
||||
"panel.chore_daily_limit_label": "Limite quotidienne",
|
||||
"panel.chore_days_label": "Jours de la semaine (vide = tous les jours)",
|
||||
"panel.chore_weekly_target_pill": "{count}×/semaine",
|
||||
"panel.chore_weekly_target_hint": "Combien de fois par semaine la tâche doit être faite, n'importe lesquels des jours choisis. Elle n'est plus proposée une fois le compte atteint et repart le lundi.",
|
||||
"panel.chore_weekly_target_label": "Objectif hebdomadaire (0 = désactivé)",
|
||||
"panel.chore_description_label": "Description (optionnel)",
|
||||
"panel.chore_enabled_label": "Activé",
|
||||
"panel.chore_first_occurrence_label": "Première occurrence",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"common.design.playroom": "Playroom",
|
||||
"common.design.console": "Console",
|
||||
"common.design.cleanpro": "Clean Pro",
|
||||
"common.design.graphite": "Grafitt",
|
||||
"panel.settings_card_design_label": "Standard kortdesign",
|
||||
"panel.settings_card_design_hint": "Stilen nye kort bruker med mindre den overstyres per kort.",
|
||||
"activity.a_chore": "en oppgave",
|
||||
@@ -55,9 +56,11 @@
|
||||
"activity.reason_bonus": "Bonus: {name}",
|
||||
"activity.reason_penalty": "Straff: {name}",
|
||||
"activity.reason_perfect_week": "Perfekt uke-bonus!",
|
||||
"activity.reason_pool_refund_assignment_changed": "Refusjon fra sparegris (belønningens tildeling endret): {name}",
|
||||
"activity.reason_pool_refund_cost_reduced": "Refusjon fra sparegris (kostnad redusert): {name}",
|
||||
"activity.reason_pool_refund_deleted": "Refusjon fra sparegris (belønning slettet): {name}",
|
||||
"activity.reason_pool_refund_expired": "Refusjon fra sparegris (belønning utløpt): {name}",
|
||||
"activity.reason_pool_refund_funding_changed": "Refusjon fra sparegris (belønningens finansiering endret): {name}",
|
||||
"activity.reason_pool_refund_sold_out": "Refusjon fra sparegris (belønning utsolgt): {name}",
|
||||
"activity.reason_streak_milestone": "Milepælsbonus ({days} dagers strek!)",
|
||||
"activity.reason_weekend_bonus": "Helgebonus (×{multiplier})",
|
||||
@@ -158,6 +161,7 @@
|
||||
"badges.entity_not_found": "{entity} ikke funnet",
|
||||
"badges.label": "Merker",
|
||||
"badges.next_up": "Neste",
|
||||
"badges.one_more": "Bare én til, så er det ditt!",
|
||||
"badges.title_with_name": "{name}s merker",
|
||||
"bonuses.add_bonus": "Legg til bonus",
|
||||
"bonuses.applying_to": "Brukes på {childName} — nåværende saldo: {points} {pointsName}",
|
||||
@@ -229,6 +233,8 @@
|
||||
"child.extra_submit": "Send",
|
||||
"child.extra_needs_desc": "Skriv først hva du gjorde.",
|
||||
"child.open_ended_tag": "Beskriv",
|
||||
"child.weekly_target_done": "Ferdig for denne uka",
|
||||
"child.weekly_target_progress": "{done}/{target} denne uka",
|
||||
"child.photo_take": "Ta et bilde",
|
||||
"child.photo_hint": "Vis den ferdige oppgaven",
|
||||
"child.photo_retake": "Ta på nytt",
|
||||
@@ -616,6 +622,9 @@
|
||||
"panel.chore_daily_cap_label": "Daglig tak (minutter, 0 = ubegrenset)",
|
||||
"panel.chore_daily_limit_label": "Daglig grense",
|
||||
"panel.chore_days_label": "Ukedager (la stå tomt = hver dag)",
|
||||
"panel.chore_weekly_target_pill": "{count}×/uke",
|
||||
"panel.chore_weekly_target_hint": "Hvor mange ganger i uka oppgaven må gjøres, på hvilke som helst av de valgte dagene. Den vises ikke lenger når antallet er nådd, og nullstilles på mandag.",
|
||||
"panel.chore_weekly_target_label": "Ukesmål (0 = av)",
|
||||
"panel.chore_description_label": "Beskrivelse (valgfritt)",
|
||||
"panel.chore_enabled_label": "Aktivert",
|
||||
"panel.chore_first_occurrence_label": "Første forekomst",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"common.design.playroom": "Playroom",
|
||||
"common.design.console": "Console",
|
||||
"common.design.cleanpro": "Clean Pro",
|
||||
"common.design.graphite": "Grafitt",
|
||||
"panel.settings_card_design_label": "Standard kortdesign",
|
||||
"panel.settings_card_design_hint": "Stilen nye kort brukar med mindre den blir overstyrt per kort.",
|
||||
"activity.a_chore": "ei oppgåve",
|
||||
@@ -55,9 +56,11 @@
|
||||
"activity.reason_bonus": "Bonus: {name}",
|
||||
"activity.reason_penalty": "Straff: {name}",
|
||||
"activity.reason_perfect_week": "Perfekt veke-bonus!",
|
||||
"activity.reason_pool_refund_assignment_changed": "Refusjon frå sparegris (løninga si tildeling endra): {name}",
|
||||
"activity.reason_pool_refund_cost_reduced": "Refusjon frå sparegris (kostnad redusert): {name}",
|
||||
"activity.reason_pool_refund_deleted": "Refusjon frå sparegris (løning sletta): {name}",
|
||||
"activity.reason_pool_refund_expired": "Refusjon frå sparegris (løning utløpt): {name}",
|
||||
"activity.reason_pool_refund_funding_changed": "Refusjon frå sparegris (løninga si finansiering endra): {name}",
|
||||
"activity.reason_pool_refund_sold_out": "Refusjon frå sparegris (løning utseld): {name}",
|
||||
"activity.reason_streak_milestone": "Milepælsbonus ({days} dagar på rad!)",
|
||||
"activity.reason_weekend_bonus": "Helgebonus (×{multiplier})",
|
||||
@@ -158,6 +161,7 @@
|
||||
"badges.entity_not_found": "{entity} ikkje funne",
|
||||
"badges.label": "Merke",
|
||||
"badges.next_up": "Neste",
|
||||
"badges.one_more": "Berre éin til, så er det ditt!",
|
||||
"badges.title_with_name": "Merka til {name}",
|
||||
"bonuses.add_bonus": "Legg til bonus",
|
||||
"bonuses.applying_to": "Vert brukt på {childName} — noverande saldo: {points} {pointsName}",
|
||||
@@ -229,6 +233,8 @@
|
||||
"child.extra_submit": "Send",
|
||||
"child.extra_needs_desc": "Skriv først kva du gjorde.",
|
||||
"child.open_ended_tag": "Beskriv",
|
||||
"child.weekly_target_done": "Ferdig for denne veka",
|
||||
"child.weekly_target_progress": "{done}/{target} denne veka",
|
||||
"child.photo_take": "Ta eit bilete",
|
||||
"child.photo_hint": "Vis den ferdige oppgåva",
|
||||
"child.photo_retake": "Ta på nytt",
|
||||
@@ -616,6 +622,9 @@
|
||||
"panel.chore_daily_cap_label": "Dagleg tak (minutt, 0 = uavgrensa)",
|
||||
"panel.chore_daily_limit_label": "Dagleg grense",
|
||||
"panel.chore_days_label": "Vekedagar (la stå tomt = kvar dag)",
|
||||
"panel.chore_weekly_target_pill": "{count}×/veke",
|
||||
"panel.chore_weekly_target_hint": "Kor mange gonger i veka oppgåva må gjerast, på kva som helst av dei valde dagane. Ho blir ikkje vist meir når talet er nådd, og blir nullstilt på måndag.",
|
||||
"panel.chore_weekly_target_label": "Vekemål (0 = av)",
|
||||
"panel.chore_description_label": "Skildring (valfri)",
|
||||
"panel.chore_enabled_label": "Aktivert",
|
||||
"panel.chore_first_occurrence_label": "Fyrste førekomst",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"common.design.playroom": "Playroom",
|
||||
"common.design.console": "Console",
|
||||
"common.design.cleanpro": "Clean Pro",
|
||||
"common.design.graphite": "Grafite",
|
||||
"panel.settings_card_design_label": "Design de card padrão",
|
||||
"panel.settings_card_design_hint": "O estilo que novos cards usam, a menos que substituído por card.",
|
||||
"activity.a_chore": "uma tarefa",
|
||||
@@ -55,9 +56,11 @@
|
||||
"activity.reason_bonus": "Bônus: {name}",
|
||||
"activity.reason_penalty": "Penalidade: {name}",
|
||||
"activity.reason_perfect_week": "Bônus de semana perfeita!",
|
||||
"activity.reason_pool_refund_assignment_changed": "Reembolso do cofrinho (atribuição da recompensa alterada): {name}",
|
||||
"activity.reason_pool_refund_cost_reduced": "Reembolso do cofrinho (custo reduzido): {name}",
|
||||
"activity.reason_pool_refund_deleted": "Reembolso do cofrinho (recompensa excluída): {name}",
|
||||
"activity.reason_pool_refund_expired": "Reembolso do cofrinho (recompensa expirada): {name}",
|
||||
"activity.reason_pool_refund_funding_changed": "Reembolso do cofrinho (financiamento da recompensa alterado): {name}",
|
||||
"activity.reason_pool_refund_sold_out": "Reembolso do cofrinho (recompensa esgotada): {name}",
|
||||
"activity.reason_streak_milestone": "Bônus de marco de sequência ({days} dias seguidos!)",
|
||||
"activity.reason_weekend_bonus": "Bônus de fim de semana (×{multiplier})",
|
||||
@@ -158,6 +161,7 @@
|
||||
"badges.entity_not_found": "{entity} não encontrado",
|
||||
"badges.label": "Conquistas",
|
||||
"badges.next_up": "A seguir",
|
||||
"badges.one_more": "Só mais um e é seu!",
|
||||
"badges.title_with_name": "Conquistas de {name}",
|
||||
"bonuses.add_bonus": "Adicionar Bónus",
|
||||
"bonuses.applying_to": "A aplicar a {childName} — saldo atual: {points} {pointsName}",
|
||||
@@ -229,6 +233,8 @@
|
||||
"child.extra_submit": "Enviar",
|
||||
"child.extra_needs_desc": "Escreva primeiro o que você fez.",
|
||||
"child.open_ended_tag": "Descrever",
|
||||
"child.weekly_target_done": "Concluída esta semana",
|
||||
"child.weekly_target_progress": "{done}/{target} esta semana",
|
||||
"child.photo_take": "Tirar uma foto",
|
||||
"child.photo_hint": "Mostre sua tarefa concluída",
|
||||
"child.photo_retake": "Repetir",
|
||||
@@ -616,6 +622,9 @@
|
||||
"panel.chore_daily_cap_label": "Limite diário (minutos, 0 = ilimitado)",
|
||||
"panel.chore_daily_limit_label": "Limite diário",
|
||||
"panel.chore_days_label": "Dias da semana (vazio = todos os dias)",
|
||||
"panel.chore_weekly_target_pill": "{count}×/semana",
|
||||
"panel.chore_weekly_target_hint": "Quantas vezes por semana a tarefa precisa ser feita, em qualquer um dos dias escolhidos. Ela deixa de aparecer quando a contagem é atingida e reinicia na segunda-feira.",
|
||||
"panel.chore_weekly_target_label": "Meta semanal (0 = desligado)",
|
||||
"panel.chore_description_label": "Descrição (opcional)",
|
||||
"panel.chore_enabled_label": "Ativada",
|
||||
"panel.chore_first_occurrence_label": "Primeira ocorrência",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"common.design.playroom": "Playroom",
|
||||
"common.design.console": "Console",
|
||||
"common.design.cleanpro": "Clean Pro",
|
||||
"common.design.graphite": "Grafite",
|
||||
"panel.settings_card_design_label": "Design de cartão predefinido",
|
||||
"panel.settings_card_design_hint": "O estilo que os novos cartões usam, salvo substituição por cartão.",
|
||||
"activity.a_chore": "uma tarefa",
|
||||
@@ -55,9 +56,11 @@
|
||||
"activity.reason_bonus": "Bónus: {name}",
|
||||
"activity.reason_penalty": "Penalidade: {name}",
|
||||
"activity.reason_perfect_week": "Bónus de semana perfeita!",
|
||||
"activity.reason_pool_refund_assignment_changed": "Reembolso do mealheiro (atribuição da recompensa alterada): {name}",
|
||||
"activity.reason_pool_refund_cost_reduced": "Reembolso do mealheiro (custo reduzido): {name}",
|
||||
"activity.reason_pool_refund_deleted": "Reembolso do mealheiro (recompensa eliminada): {name}",
|
||||
"activity.reason_pool_refund_expired": "Reembolso do mealheiro (recompensa expirada): {name}",
|
||||
"activity.reason_pool_refund_funding_changed": "Reembolso do mealheiro (financiamento da recompensa alterado): {name}",
|
||||
"activity.reason_pool_refund_sold_out": "Reembolso do mealheiro (recompensa esgotada): {name}",
|
||||
"activity.reason_streak_milestone": "Bónus de marco de sequência ({days} dias seguidos!)",
|
||||
"activity.reason_weekend_bonus": "Bónus de fim de semana (×{multiplier})",
|
||||
@@ -158,6 +161,7 @@
|
||||
"badges.entity_not_found": "{entity} não encontrado",
|
||||
"badges.label": "Conquistas",
|
||||
"badges.next_up": "A seguir",
|
||||
"badges.one_more": "Só mais um e é teu!",
|
||||
"badges.title_with_name": "Conquistas de {name}",
|
||||
"bonuses.add_bonus": "Adicionar Bónus",
|
||||
"bonuses.applying_to": "A aplicar a {childName} — saldo atual: {points} {pointsName}",
|
||||
@@ -229,6 +233,8 @@
|
||||
"child.extra_submit": "Enviar",
|
||||
"child.extra_needs_desc": "Escreve primeiro o que fizeste.",
|
||||
"child.open_ended_tag": "Descrever",
|
||||
"child.weekly_target_done": "Concluída esta semana",
|
||||
"child.weekly_target_progress": "{done}/{target} esta semana",
|
||||
"child.photo_take": "Tirar uma foto",
|
||||
"child.photo_hint": "Mostra a tua tarefa terminada",
|
||||
"child.photo_retake": "Repetir",
|
||||
@@ -621,6 +627,9 @@
|
||||
"panel.chore_daily_cap_label": "Limite diário (minutos, 0 = ilimitado)",
|
||||
"panel.chore_daily_limit_label": "Limite diário",
|
||||
"panel.chore_days_label": "Dias da semana (deixe vazio = todos os dias)",
|
||||
"panel.chore_weekly_target_pill": "{count}×/semana",
|
||||
"panel.chore_weekly_target_hint": "Quantas vezes por semana a tarefa tem de ser feita, em qualquer um dos dias escolhidos. Deixa de ser apresentada quando a contagem é atingida e reinicia à segunda-feira.",
|
||||
"panel.chore_weekly_target_label": "Meta semanal (0 = desligado)",
|
||||
"panel.chore_description_label": "Descrição (opcional)",
|
||||
"panel.chore_enabled_label": "Ativada",
|
||||
"panel.chore_first_occurrence_label": "Primeira ocorrência",
|
||||
|
||||
@@ -73,6 +73,8 @@ class TaskMateActivityCard extends LitElement {
|
||||
['Pool refund (reward sold out):', 'activity.reason_pool_refund_sold_out'],
|
||||
['Pool refund (reward cost reduced):', 'activity.reason_pool_refund_cost_reduced'],
|
||||
['Pool refund (reward deleted):', 'activity.reason_pool_refund_deleted'],
|
||||
['Pool refund (reward funding changed):', 'activity.reason_pool_refund_funding_changed'],
|
||||
['Pool refund (reward assignment changed):', 'activity.reason_pool_refund_assignment_changed'],
|
||||
['Penalty:', 'activity.reason_penalty'],
|
||||
['Bonus:', 'activity.reason_bonus'],
|
||||
];
|
||||
|
||||
@@ -603,7 +603,9 @@ class TaskMateApprovalsCard extends LitElement {
|
||||
.ap-cn-photo img { width: 100%; height: 100%; object-fit: cover; }
|
||||
|
||||
/* Clean Pro — divided list */
|
||||
.ap-cp-item { padding: 10px 0; }
|
||||
.ap-cp-item { padding: 10px 0; flex-wrap: wrap; row-gap: 8px; }
|
||||
.ap-cp-mid { flex: 1 1 130px; min-width: 130px; }
|
||||
.ap-cp-actions { display: flex; gap: 8px; flex: 0 0 auto; margin-left: auto; }
|
||||
.ap-cp-title { font-weight: 600; }
|
||||
.ap-cp-sub { font-size: 12px; }
|
||||
.ap-cp-photo { width: 32px; height: 32px; border-radius: 8px; overflow: hidden;
|
||||
@@ -1048,13 +1050,13 @@ class TaskMateApprovalsCard extends LitElement {
|
||||
return html`
|
||||
<div class="row ap-cp-item" style="--ac:${tone}">
|
||||
${this._av(it.childName, this._childAvatar(it.childId), tone, 38)}
|
||||
<div style="flex:1;min-width:0">
|
||||
<div class="ap-cp-mid">
|
||||
<div class="ap-cp-title">${it.title}</div>
|
||||
<div class="muted ap-cp-sub">${it.childName}${time ? html` · ${time}` : ""}</div>
|
||||
</div>
|
||||
${this._apPhotoDesigned(it, "ap-cp-photo")}
|
||||
<span class="chip soft ap-gold-soft">+${it.points}</span>
|
||||
${this._designActions(it, "ghost")}
|
||||
<div class="ap-cp-actions">${this._designActions(it, "ghost")}</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
|
||||
@@ -1716,6 +1716,12 @@ class TaskMateChildCard extends LitElement {
|
||||
border-radius: 3px;
|
||||
transition: width 0.4s ease;
|
||||
}
|
||||
.next-badge-nudge {
|
||||
margin-top: 5px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════
|
||||
DESIGNED STYLES (playroom / console / cleanpro)
|
||||
@@ -1886,6 +1892,9 @@ class TaskMateChildCard extends LitElement {
|
||||
.tmd-next-badge .bar i {
|
||||
display: block; height: 100%; border-radius: 999px; transition: width .4s ease;
|
||||
}
|
||||
.tmd-next-badge .nudge {
|
||||
margin-top: 5px; font-size: 11px; font-weight: 800; color: var(--tmd-accent);
|
||||
}
|
||||
/* Playroom is chunkier, console squares everything off, accessible needs
|
||||
a solid ring rather than a dashed one to stay legible. */
|
||||
:host([data-tm-design="playroom"]) .tmd-next-badge .bar { height: 8px; }
|
||||
@@ -1895,6 +1904,7 @@ class TaskMateChildCard extends LitElement {
|
||||
:host([data-tm-design="accessible"]) .tmd-next-badge .bar { height: 10px; }
|
||||
:host([data-tm-design="accessible"]) .tmd-next-badge .nm,
|
||||
:host([data-tm-design="accessible"]) .tmd-next-badge .cnt { font-size: 14px; }
|
||||
:host([data-tm-design="accessible"]) .tmd-next-badge .nudge { font-size: 13px; }
|
||||
|
||||
/* Designed: vacation banner + swappable section */
|
||||
.tmd-vacation {
|
||||
@@ -1928,6 +1938,7 @@ class TaskMateChildCard extends LitElement {
|
||||
show_due_days_only: true, // Whether to apply due_days filtering at all
|
||||
show_badges: true, // Show badge strip between points and chores
|
||||
show_next_badge: true, // Show progress toward the closest unearned badge
|
||||
show_badge_nudge: true, // Encouraging line when one away from that badge
|
||||
header_color: '#9b59b6',
|
||||
...config,
|
||||
};
|
||||
@@ -2131,6 +2142,9 @@ class TaskMateChildCard extends LitElement {
|
||||
aria-valuenow="${nextBadge.pct}" aria-valuemin="0" aria-valuemax="100">
|
||||
<i style="width: ${nextBadge.pct}%; background: ${this._tierColor(nextBadge.badge.tier)}"></i>
|
||||
</div>
|
||||
${this._badgeNudgeText(nextBadge)
|
||||
? html`<div class="next-badge-nudge">${this._badgeNudgeText(nextBadge)}</div>`
|
||||
: ''}
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
@@ -2249,6 +2263,21 @@ class TaskMateChildCard extends LitElement {
|
||||
return best;
|
||||
}
|
||||
|
||||
/* "Nearly there" (#890) — the line of encouragement under the next-badge bar.
|
||||
|
||||
It fires on the last remaining unit and nothing else, measured in the
|
||||
criterion's own units rather than percent: 90% of a ten-week badge is one
|
||||
week away, 90% of a hundred-point badge is ten points away. Rare on
|
||||
purpose — a line that shows for half the climb has stopped meaning
|
||||
anything by the time it matters. Manual-award badges carry no criterion
|
||||
and so never nudge. */
|
||||
_badgeNudgeText(nextBadge) {
|
||||
if (this.config.show_badge_nudge === false) return "";
|
||||
const c = nextBadge?.badge?.closest_criterion;
|
||||
if (!c) return "";
|
||||
return Number(c.target) - Number(c.current) === 1 ? this._t("badges.one_more") : "";
|
||||
}
|
||||
|
||||
// Built-in badge names arrive from the sensor in English; the localised
|
||||
// name lives under badge.name_<suffix> (same scheme as the panel).
|
||||
_badgeName(b) {
|
||||
@@ -2307,6 +2336,23 @@ class TaskMateChildCard extends LitElement {
|
||||
}
|
||||
|
||||
/** Mirror of _renderChoreCard's "completed today" detection, designed branch only. */
|
||||
// How many times this chore can still be ticked TODAY.
|
||||
//
|
||||
// Normally the chore's own daily_limit, clamped to 1 for a first_come race.
|
||||
// A weekly target (#883) clamps it again: once the week's quota is full there
|
||||
// is no allowance left, so a chore with daily_limit 5 and 2/2 done reads as
|
||||
// finished rather than staying tappable — the tap would only be one
|
||||
// complete_chore refuses (#805). Only applied once the child has ticked it
|
||||
// today; a quota filled on earlier days leaves the row locked instead, so a
|
||||
// green "done" row never appears with no completion behind it to undo.
|
||||
_effectiveDailyLimit(chore, child, countToday, isFirstCome) {
|
||||
const limit = isFirstCome ? 1 : (chore.daily_limit || 1);
|
||||
const target = Number(chore.weekly_target) || 0;
|
||||
if (target <= 0 || countToday <= 0) return limit;
|
||||
const doneThisWeek = Number((child?.weekly_chore_progress || {})[chore.id] || 0);
|
||||
return Math.min(limit, countToday + Math.max(0, target - doneThisWeek));
|
||||
}
|
||||
|
||||
_isChoreDone(chore, child, todaysCompletions) {
|
||||
// async_parent_complete_chore writes child_id="__parent__" to dismiss a
|
||||
// chore for the WHOLE pool with zero points — it never credits a specific
|
||||
@@ -2336,7 +2382,7 @@ class TaskMateChildCard extends LitElement {
|
||||
// the filter and the backend both clamp it to 1 (coord_assignments). Reading
|
||||
// the raw daily_limit here would leave the winner's own row looking incomplete
|
||||
// and tappable, and complete_chore would then silently no-op the tap (#805).
|
||||
const done = count >= (isFirstCome ? 1 : (chore.daily_limit || 1));
|
||||
const done = count >= this._effectiveDailyLimit(chore, child, count, isFirstCome);
|
||||
return { done, completions: childCompletionsToday };
|
||||
}
|
||||
|
||||
@@ -2395,8 +2441,11 @@ class TaskMateChildCard extends LitElement {
|
||||
// dim/show only — hide is filtered upstream same as recurrence). Never true
|
||||
// for the winner's own row: _isFirstComeLocked is only set on siblings.
|
||||
const firstComeLocked = !!chore._isFirstComeLocked && !done;
|
||||
// Weekly quota filled — nothing owed until Monday, so the tap is refused
|
||||
// the same way a recurrence window refuses one.
|
||||
const weeklyDone = !!chore._weeklyTargetMet && !done;
|
||||
const onAct = () => {
|
||||
if (loading || blocked || recLocked || firstComeLocked) return;
|
||||
if (loading || blocked || recLocked || firstComeLocked || weeklyDone) return;
|
||||
if (done) this._handleUndo(chore, child, completions);
|
||||
else this._handleComplete(chore, child);
|
||||
};
|
||||
@@ -2409,10 +2458,11 @@ class TaskMateChildCard extends LitElement {
|
||||
(blocked && dependencyMode === "dim") ||
|
||||
(chore._isRecurrenceLocked && recurrenceDoneMode === "dim") ||
|
||||
(chore._isFirstComeLocked && firstComeClaimedMode === "dim") ||
|
||||
weeklyDone ||
|
||||
chore._isLockedPreview === true
|
||||
);
|
||||
return {
|
||||
chore, child, done, loading, onAct, index: i, dimmed, blocked, recLocked, firstComeLocked,
|
||||
chore, child, done, loading, onAct, index: i, dimmed, blocked, recLocked, firstComeLocked, weeklyDone,
|
||||
tone: this._designTone(i),
|
||||
glyph: this._choreGlyph(chore),
|
||||
points: chore.effective_points ?? chore.points,
|
||||
@@ -2494,6 +2544,9 @@ class TaskMateChildCard extends LitElement {
|
||||
aria-valuenow="${nextBadge.pct}" aria-valuemin="0" aria-valuemax="100">
|
||||
<i style="width:${nextBadge.pct}%;background:${this._tierColor(nextBadge.badge.tier)}"></i>
|
||||
</div>
|
||||
${this._badgeNudgeText(nextBadge)
|
||||
? html`<div class="nudge">${this._badgeNudgeText(nextBadge)}</div>`
|
||||
: ""}
|
||||
</div>
|
||||
</div>` : ""}
|
||||
${sectionLine}
|
||||
@@ -2559,8 +2612,13 @@ class TaskMateChildCard extends LitElement {
|
||||
? this._t("child.claimed_by_another", { name: r.chore._firstComeCompletedByName })
|
||||
: this._t("child.claimed_by_another_generic"))
|
||||
: "";
|
||||
// Weekly target (#883): shown from the first tick so the child can see
|
||||
// where they are, not only once the quota is full.
|
||||
const weeklyLabel = r.chore._weeklyTarget > 0
|
||||
? this._t("child.weekly_target_progress", { done: r.chore._weeklyProgress, target: r.chore._weeklyTarget })
|
||||
: "";
|
||||
return html`
|
||||
${r.mandatory || r.photo || r.openEnded || depNames.length || recLabel || firstComeLabel ? html`
|
||||
${r.mandatory || r.photo || r.openEnded || depNames.length || recLabel || firstComeLabel || weeklyLabel ? 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>` : ""}
|
||||
@@ -2568,6 +2626,7 @@ class TaskMateChildCard extends LitElement {
|
||||
${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>` : ""}
|
||||
${firstComeLabel ? html`<span class="tmd-tag">✅ ${firstComeLabel}</span>` : ""}
|
||||
${weeklyLabel ? html`<span class="tmd-tag">📅 ${weeklyLabel}</span>` : ""}
|
||||
</div>` : ""}
|
||||
${showDesc ? html`<div class="tmd-desc">${r.chore.description}</div>` : ""}`;
|
||||
}
|
||||
@@ -2583,7 +2642,7 @@ class TaskMateChildCard extends LitElement {
|
||||
|
||||
_designDoneBtn(r, label, cls) {
|
||||
return html`<button class="btn ${cls || ""}"
|
||||
?disabled=${r.loading || r.blocked || r.recLocked || r.firstComeLocked}
|
||||
?disabled=${r.loading || r.blocked || r.recLocked || r.firstComeLocked || r.weeklyDone}
|
||||
@click=${(e) => { e.stopPropagation(); r.onAct(); }}>${label}</button>`;
|
||||
}
|
||||
|
||||
@@ -2990,6 +3049,20 @@ class TaskMateChildCard extends LitElement {
|
||||
chore._isRecurrenceLocked = chore._isRecurring && !availableNow && !completedToday;
|
||||
if (chore._isRecurrenceLocked && recurrenceDoneMode === 'hide') return false;
|
||||
|
||||
// Weekly target (#883): the child picks which days, so the chore keeps
|
||||
// being offered until the week's quota is filled. The count comes from
|
||||
// the per-child map on the overview sensor — the card only ever sees
|
||||
// today's completions, so it can't total a week by itself. Excluding
|
||||
// completedToday keeps the row live for an undo when today's tick was
|
||||
// the one that filled it, exactly as the recurrence lock does.
|
||||
chore._weeklyTarget = Number(chore.weekly_target) || 0;
|
||||
chore._weeklyProgress = chore._weeklyTarget > 0
|
||||
? Number((child.weekly_chore_progress || {})[chore.id] || 0)
|
||||
: 0;
|
||||
chore._weeklyTargetMet = chore._weeklyTarget > 0
|
||||
&& chore._weeklyProgress >= chore._weeklyTarget
|
||||
&& !completedToday;
|
||||
|
||||
// Mark whether this chore's time period has elapsed (grace-aware).
|
||||
chore._isTimeElapsed = this._isTimePeriodElapsed(chore);
|
||||
|
||||
@@ -3494,7 +3567,7 @@ class TaskMateChildCard extends LitElement {
|
||||
// Clamped for first_come the same way the filter and the backend clamp it: a
|
||||
// stored daily_limit > 1 must not make the winner's finished row read as still
|
||||
// outstanding, or it invites a tap that complete_chore silently swallows (#805).
|
||||
const dailyLimit = isFirstCome ? 1 : (chore.daily_limit || 1);
|
||||
const dailyLimit = this._effectiveDailyLimit(chore, child, completionsToday, isFirstCome);
|
||||
|
||||
// Check for optimistic completions (chores just completed but not yet confirmed by HA)
|
||||
const optimisticKey = `${chore.id}_${child.id}`;
|
||||
@@ -3547,6 +3620,9 @@ class TaskMateChildCard extends LitElement {
|
||||
const firstComeClaimedMode = this.config.first_come_claimed_mode || 'hide';
|
||||
const firstComeLocked = !!chore._isFirstComeLocked && !isCompletedForToday;
|
||||
const notAvailableFirstCome = firstComeLocked && firstComeClaimedMode === 'dim';
|
||||
// Weekly quota filled (#883). Dimmed and non-interactive: the backend
|
||||
// would refuse the completion, so a live row would just swallow the tap.
|
||||
const weeklyDone = !!chore._weeklyTargetMet && !isCompletedForToday;
|
||||
// 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';
|
||||
@@ -3570,13 +3646,14 @@ class TaskMateChildCard extends LitElement {
|
||||
if (depBlocked) return; // Prerequisite chore not approved yet
|
||||
if (timeElapsed) return; // Time period passed — not interactive
|
||||
if (firstComeLocked) return; // Claimed by another child today — not interactive
|
||||
if (weeklyDone) return; // Weekly target already met — nothing owed until Monday
|
||||
if (isCompletedForToday) {
|
||||
this._handleUndo(chore, child, childCompletionsToday);
|
||||
} else {
|
||||
this._handleComplete(chore, child);
|
||||
}
|
||||
};
|
||||
const isInteractive = !(isLoading || notDueToday || recurrenceLocked || isLockedPreview || depBlocked || timeElapsed || firstComeLocked);
|
||||
const isInteractive = !(isLoading || notDueToday || recurrenceLocked || isLockedPreview || depBlocked || timeElapsed || firstComeLocked || weeklyDone);
|
||||
const handleRowKeyDown = (e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
@@ -3596,13 +3673,15 @@ class TaskMateChildCard extends LitElement {
|
||||
? (chore._firstComeCompletedByName
|
||||
? this._t('child.claimed_by_another', { name: chore._firstComeCompletedByName })
|
||||
: this._t('child.claimed_by_another_generic'))
|
||||
: weeklyDone
|
||||
? this._t('child.weekly_target_done')
|
||||
: isCompletedForToday
|
||||
? this._t('child.click_to_undo')
|
||||
: this._t('child.click_to_complete');
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="chore-card ${chore.mandatory ? "mandatory" : ""} ${isLoading ? "loading" : ""} ${isCelebrating ? "celebrating" : ""} ${isCompletedForToday ? "completed" : ""} ${notDueToday ? "not-due-today" : ""} ${notAvailableRecurrence ? "recurrence-unavailable" : ""} ${notAvailableFirstCome ? "first-come-unavailable" : ""} ${timeElapsed ? "time-elapsed" : ""} ${isLockedPreview ? "chore-locked" : ""} ${depDimmed ? "dependency-blocked" : ""}"
|
||||
class="chore-card ${chore.mandatory ? "mandatory" : ""} ${isLoading ? "loading" : ""} ${isCelebrating ? "celebrating" : ""} ${isCompletedForToday ? "completed" : ""} ${notDueToday ? "not-due-today" : ""} ${notAvailableRecurrence || weeklyDone ? "recurrence-unavailable" : ""} ${notAvailableFirstCome ? "first-come-unavailable" : ""} ${timeElapsed ? "time-elapsed" : ""} ${isLockedPreview ? "chore-locked" : ""} ${depDimmed ? "dependency-blocked" : ""}"
|
||||
role="button"
|
||||
tabindex="${isInteractive ? '0' : '-1'}"
|
||||
aria-disabled="${isInteractive ? 'false' : 'true'}"
|
||||
@@ -3646,6 +3725,7 @@ class TaskMateChildCard extends LitElement {
|
||||
<ha-icon icon="${pointsIcon}"></ha-icon>
|
||||
+${chore.effective_points ?? chore.points}
|
||||
${dailyLimit > 1 ? html`<span style="font-size: 0.8em; opacity: 0.7;">(${completionsToday}/${dailyLimit})</span>` : ''}
|
||||
${chore._weeklyTarget > 0 ? html`<span style="font-size: 0.8em; opacity: 0.7;">${this._t('child.weekly_target_progress', { done: chore._weeklyProgress, target: chore._weeklyTarget })}</span>` : ''}
|
||||
</div>
|
||||
${chore.require_photo && !isCompletedForToday ? html`
|
||||
<div class="recurrence-label">
|
||||
|
||||
@@ -13,11 +13,21 @@
|
||||
* console — dark gamified HUD
|
||||
* cleanpro — calm productivity / SaaS
|
||||
* accessible — high contrast, colour-blind safe, dyslexia-friendly type
|
||||
* graphite — corporate/system look; reuses the cleanpro LAYOUT (see
|
||||
* LAYOUT_ALIAS) and restyles it entirely through tokens + kit
|
||||
*
|
||||
* Mirrors window.__taskmate_localize: exposed globally, no ES module imports.
|
||||
*/
|
||||
(function () {
|
||||
const IDS = ["classic", "playroom", "console", "cleanpro", "accessible"];
|
||||
const IDS = ["classic", "playroom", "console", "cleanpro", "accessible", "graphite"];
|
||||
|
||||
// A design whose markup is another design's. apply() stamps the real id on
|
||||
// the host (so the style's CSS applies) but hands cards the layout id, so
|
||||
// every existing `design === "cleanpro"` branch picks graphite up too. Without
|
||||
// this a new id falls down each card's final `else`, which is playroom in
|
||||
// some cards and cleanpro in others — the classic "works on one style,
|
||||
// invisible on the rest" bug.
|
||||
const LAYOUT_ALIAS = { graphite: "cleanpro" };
|
||||
|
||||
// Fonts must be loaded at the document level (an @import inside a shadow
|
||||
// stylesheet is honoured, but loading once globally avoids N duplicate fetches).
|
||||
@@ -56,6 +66,23 @@
|
||||
--tmd-c1:#4F6BED;--tmd-c2:#0FB5A8;--tmd-c3:#E0A100;--tmd-c4:#7A5AF0;--tmd-c5:#E0567A;--tmd-c6:#2BA84A;
|
||||
}
|
||||
|
||||
:host([data-tm-design="graphite"]),[data-tm-design="graphite"]{
|
||||
/* Neutrals carry the card; the only saturated element is the header band
|
||||
(per-card header_color). Actions are near-black rather than a coloured
|
||||
accent, and colour is reserved for meaning: good / warn / bad. */
|
||||
--tmd-bg:#EFEFF2;--tmd-surface:#FFFFFF;--tmd-surface-2:#F5F5F7;--tmd-border:#D2D2D7;
|
||||
--tmd-text:#1D1D1F;--tmd-dim:#6E6E73;
|
||||
--tmd-accent:#1D1D1F;--tmd-accent2:#6E6E73;--tmd-good:#1D7A4C;--tmd-warn:#B25E00;--tmd-bad:#B3261E;--tmd-gold:#B25E00;
|
||||
--tmd-radius:18px;--tmd-radius-sm:12px;--tmd-shadow:none;--tmd-hd-text:#fff;
|
||||
/* The system stack resolves to real SF Pro on Apple hardware; Inter (already
|
||||
in FONT_IMPORT) is the fallback everywhere else. Figures are kept aligned
|
||||
with font-variant-numeric rather than a separate monospace face. */
|
||||
--tmd-font-display:-apple-system,BlinkMacSystemFont,"SF Pro Display","Inter",system-ui,sans-serif;
|
||||
--tmd-font-body:-apple-system,BlinkMacSystemFont,"SF Pro Text","Inter",system-ui,sans-serif;
|
||||
--tmd-font-mono:-apple-system,BlinkMacSystemFont,"SF Pro Text","Inter",system-ui,sans-serif;
|
||||
--tmd-c1:#1F5F8B;--tmd-c2:#146356;--tmd-c3:#8A4B12;--tmd-c4:#3E4B8B;--tmd-c5:#6B3D6B;--tmd-c6:#2F4858;
|
||||
}
|
||||
|
||||
/* ── Dark-mode variants ──────────────────────────────────────────────────
|
||||
Cards stamp data-tm-dark on the host when HA is in dark mode. Each design has
|
||||
a light base (above) and a dark override here, so all three follow HA's
|
||||
@@ -93,6 +120,16 @@
|
||||
--tmd-text:#E6EAF1;--tmd-dim:#97A1B0;--tmd-accent:#6E86F5;--tmd-accent2:#1FC7B8;
|
||||
--tmd-shadow:0 1px 2px rgba(0,0,0,.4),0 8px 18px rgba(0,0,0,.35);
|
||||
}
|
||||
:host([data-tm-design="graphite"][data-tm-dark]),[data-tm-design="graphite"][data-tm-dark]{
|
||||
/* True black behind elevated sheets. --tmd-accent stays a mid neutral so a
|
||||
card with no header_color still has a legible band with white text; the
|
||||
button colour is derived from --tmd-text instead, so it inverts properly. */
|
||||
--tmd-bg:#000000;--tmd-surface:#1C1C1E;--tmd-surface-2:#2C2C2E;--tmd-border:#38383A;
|
||||
--tmd-text:#F5F5F7;--tmd-dim:#98989F;
|
||||
--tmd-accent:#3A3A3C;--tmd-accent2:#98989F;--tmd-good:#30A46C;--tmd-warn:#D98324;--tmd-bad:#E5484D;--tmd-gold:#D98324;
|
||||
--tmd-shadow:none;
|
||||
--tmd-c1:#2C7CB0;--tmd-c2:#1E8C77;--tmd-c3:#B06A26;--tmd-c4:#5B6AC0;--tmd-c5:#945993;--tmd-c6:#4A6B80;
|
||||
}
|
||||
:host([data-tm-design="accessible"][data-tm-dark]),[data-tm-design="accessible"][data-tm-dark]{
|
||||
/* Pure black would bloom on OLED and is harsh for astigmatism; #0A0A0A with
|
||||
near-white text still clears AAA. Hues are the Okabe-Ito light variants,
|
||||
@@ -145,7 +182,142 @@
|
||||
.stat{background:var(--tmd-surface-2);border:1px solid var(--tmd-border);border-radius:var(--tmd-radius-sm);padding:10px 11px}
|
||||
.stat .k{font-size:11px;font-weight:700;color:var(--tmd-dim);text-transform:uppercase;letter-spacing:.04em}
|
||||
.stat .v{font-family:var(--tmd-font-display);font-weight:800;font-size:20px;margin-top:2px}
|
||||
.grid{display:grid;gap:10px}`;
|
||||
.grid{display:grid;gap:10px}
|
||||
|
||||
/* ── Graphite ─────────────────────────────────────────────────────────────
|
||||
Graphite has no render path of its own: it borrows the Clean Pro markup
|
||||
(LAYOUT_ALIAS) and is defined entirely by the rules below, so all cards
|
||||
adopt it at once. Selectors are :host([...]) + class, which out-specifies
|
||||
the '.tmd-check .c-num' pattern the cards use for their own design CSS. */
|
||||
:host([data-tm-design="graphite"]) .tmd{border:1px solid var(--tmd-border);box-shadow:none}
|
||||
/* The header band stays full colour — it is the one saturated element. */
|
||||
:host([data-tm-design="graphite"]) .tmd-hd{padding:13px 15px 12px}
|
||||
:host([data-tm-design="graphite"]) .tmd-hd .tt{font-weight:600;font-size:17px;letter-spacing:-.42px}
|
||||
:host([data-tm-design="graphite"]) .tmd-hd .tt small{font-weight:400;font-size:13px;opacity:.78;letter-spacing:-.06px;margin-top:2px}
|
||||
:host([data-tm-design="graphite"]) .tmd-hd .ic{border-radius:50%;background:rgba(255,255,255,.2)}
|
||||
:host([data-tm-design="graphite"]) .tmd-hd .pill{background:transparent;padding:0;font-weight:500;font-size:15px;letter-spacing:-.2px;text-transform:none}
|
||||
:host([data-tm-design="graphite"]) .tmd-hd .cnt{background:rgba(255,255,255,.22);font-weight:500;font-variant-numeric:tabular-nums}
|
||||
|
||||
/* Actions are near-black on light and near-white on dark. Deriving them from
|
||||
--tmd-text (not --tmd-accent) is what makes both directions invert. */
|
||||
:host([data-tm-design="graphite"]) .btn{background:var(--tmd-text);color:var(--tmd-surface);
|
||||
font-family:var(--tmd-font-body);font-weight:500;letter-spacing:-.2px;
|
||||
border-radius:9px;box-shadow:none}
|
||||
:host([data-tm-design="graphite"]) .btn.ghost{background:transparent;color:var(--tmd-text);border:1px solid var(--tmd-border)}
|
||||
/* "good" marks the primary confirm (Approve / Dismiss). Graphite draws the
|
||||
primary action in near-black like every other one; green is reserved for
|
||||
reporting a completed state, not for a button. */
|
||||
:host([data-tm-design="graphite"]) .btn.good{background:var(--tmd-text);color:var(--tmd-surface)}
|
||||
:host([data-tm-design="graphite"]) .btn.bad{background:transparent;color:var(--tmd-bad)}
|
||||
:host([data-tm-design="graphite"]) .btn.round{border-radius:50%}
|
||||
|
||||
/* Figures hold their column as they tick over. */
|
||||
:host([data-tm-design="graphite"]) .num,
|
||||
:host([data-tm-design="graphite"]) .big,
|
||||
:host([data-tm-design="graphite"]) .stat .v{font-variant-numeric:tabular-nums;font-weight:600;letter-spacing:-.6px}
|
||||
/* A chip reports a value here; it is not a highlight. */
|
||||
:host([data-tm-design="graphite"]) .chip{background:transparent;border-color:transparent;color:var(--tmd-dim);
|
||||
font-weight:500;font-variant-numeric:tabular-nums}
|
||||
:host([data-tm-design="graphite"]) .chip.soft{color:var(--tmd-dim)}
|
||||
/* 4px track rather than a fat rounded pill. */
|
||||
:host([data-tm-design="graphite"]) .bar{height:4px;border:0;border-radius:2px;background:var(--tmd-border)}
|
||||
:host([data-tm-design="graphite"]) .bar>i{box-shadow:none}
|
||||
:host([data-tm-design="graphite"]) .av{box-shadow:none}
|
||||
:host([data-tm-design="graphite"]) .divide{background:var(--tmd-border)}
|
||||
/* Stat cells are separated by hairlines, not drawn as nested boxes. */
|
||||
:host([data-tm-design="graphite"]) .stat{background:transparent;border:0;border-radius:0;padding:0 12px}
|
||||
:host([data-tm-design="graphite"]) .stat .k{text-transform:none;letter-spacing:-.04px;font-weight:400;font-size:12.5px}
|
||||
|
||||
/* Grouped inset list: one hairline container, separators starting past the
|
||||
leading control, and the leading badge reduced to a completion mark. */
|
||||
:host([data-tm-design="graphite"]) .tmd-checklist{border:1px solid var(--tmd-border);border-radius:var(--tmd-radius-sm)}
|
||||
:host([data-tm-design="graphite"]) .tmd-check{position:relative;padding:9px 12px;min-height:46px;box-sizing:border-box}
|
||||
:host([data-tm-design="graphite"]) .tmd-check+.tmd-check{border-top:0}
|
||||
:host([data-tm-design="graphite"]) .tmd-check+.tmd-check::before{content:"";position:absolute;top:0;left:46px;right:0;height:1px;background:var(--tmd-border)}
|
||||
:host([data-tm-design="graphite"]) .tmd-check .c-num{width:22px;height:22px;border-radius:50%;
|
||||
background:transparent;border:1.5px solid var(--tmd-border);color:transparent;font-size:0}
|
||||
:host([data-tm-design="graphite"]) .tmd-check.done .c-num{background:var(--tmd-good);border-color:var(--tmd-good);color:#fff;font-size:13px}
|
||||
:host([data-tm-design="graphite"]) .tmd-check .c-name{font-weight:450;font-size:15px;letter-spacing:-.18px}
|
||||
/* Dimming already says "done"; the strike-through is redundant noise. */
|
||||
:host([data-tm-design="graphite"]) .tmd-check.done .c-name{text-decoration:none}
|
||||
:host([data-tm-design="graphite"]) .tmd-check.done{opacity:.52}
|
||||
/* Meta reads as quiet text, not as a row of coloured pills. */
|
||||
:host([data-tm-design="graphite"]) .tmd-tag{background:transparent;border-color:transparent;
|
||||
color:var(--tmd-dim);font-weight:400;font-size:12.5px;letter-spacing:-.02px;padding:0;text-transform:none}
|
||||
:host([data-tm-design="graphite"]) .tmd-tag.mandatory{color:var(--tmd-bad)}
|
||||
:host([data-tm-design="graphite"]) .tmd-tag.photo{color:var(--tmd-dim)}
|
||||
|
||||
/* The decorative emoji tile in a card header is the loudest "kids app" tell
|
||||
left in the designed markup, and it is chrome rather than user data — the
|
||||
child card puts a real avatar here instead, which is kept. */
|
||||
:host([data-tm-design="graphite"]) .tmd-hd .ic{display:none}
|
||||
/* Sentence case throughout: tracked-out uppercase labels are not this style. */
|
||||
:host([data-tm-design="graphite"]) .lbl,
|
||||
:host([data-tm-design="graphite"]) .ap-group-label,
|
||||
:host([data-tm-design="graphite"]) .rw-jackpot-label,
|
||||
:host([data-tm-design="graphite"]) .tmd-badges .lbl{text-transform:none;letter-spacing:-.04px;font-weight:500;font-size:13px;color:var(--tmd-dim)}
|
||||
|
||||
/* Badge strip + next-badge: outlines rather than filled colour discs. */
|
||||
:host([data-tm-design="graphite"]) .tmd-badges,
|
||||
:host([data-tm-design="graphite"]) .tmd-next-badge{background:transparent;border:1px solid var(--tmd-border);border-radius:var(--tmd-radius-sm)}
|
||||
:host([data-tm-design="graphite"]) .tmd-badge-mini{background:transparent;border:1px solid var(--tmd-border);box-shadow:none}
|
||||
/* The glyphs inside these discs are drawn white for a filled tier colour;
|
||||
once the fill is gone they have to take the text colour or they vanish. */
|
||||
:host([data-tm-design="graphite"]) .tmd-badge-mini,
|
||||
:host([data-tm-design="graphite"]) .tmd-badge-mini ha-icon,
|
||||
:host([data-tm-design="graphite"]) .tmd-next-badge .ic,
|
||||
:host([data-tm-design="graphite"]) .tmd-next-badge .ic ha-icon{color:var(--tmd-text)}
|
||||
:host([data-tm-design="graphite"]) .tmd-next-badge .ic{background:transparent;box-shadow:none}
|
||||
:host([data-tm-design="graphite"]) .tmd-next-badge .bar{height:4px;border:0;background:var(--tmd-border)}
|
||||
:host([data-tm-design="graphite"]) .tmd-next-badge .nm{font-weight:500}
|
||||
|
||||
/* Points display: tiles become a plain figure block. */
|
||||
:host([data-tm-design="graphite"]) .cp-tile{background:transparent;border:1px solid var(--tmd-border);border-radius:var(--tmd-radius-sm);box-shadow:none}
|
||||
:host([data-tm-design="graphite"]) .cp-pts,
|
||||
:host([data-tm-design="graphite"]) .cp-total{font-variant-numeric:tabular-nums;font-weight:600;letter-spacing:-1.2px}
|
||||
:host([data-tm-design="graphite"]) .cp-name{font-weight:500;letter-spacing:-.18px}
|
||||
:host([data-tm-design="graphite"]) .cp-rank,
|
||||
:host([data-tm-design="graphite"]) .cp-chips .chip{background:transparent;border-color:transparent;color:var(--tmd-dim);font-weight:500}
|
||||
|
||||
/* Rewards: a grouped list of fixed costs, not a deck of tinted cards. */
|
||||
:host([data-tm-design="graphite"]) .rw-list{border:1px solid var(--tmd-border);border-radius:var(--tmd-radius-sm);gap:0}
|
||||
:host([data-tm-design="graphite"]) .rw-card{background:transparent;border:0;border-radius:0;box-shadow:none}
|
||||
:host([data-tm-design="graphite"]) .rw-card+.rw-card{border-top:1px solid var(--tmd-border)}
|
||||
:host([data-tm-design="graphite"]) .rw-name{font-weight:500;letter-spacing:-.18px}
|
||||
:host([data-tm-design="graphite"]) .rw-cost{font-variant-numeric:tabular-nums;font-weight:500;color:var(--tmd-dim)}
|
||||
:host([data-tm-design="graphite"]) .rw-emoji{font-size:0}
|
||||
:host([data-tm-design="graphite"]) .rw-pool-bar{height:4px;border:0;background:var(--tmd-border)}
|
||||
:host([data-tm-design="graphite"]) .rw-jackpot{background:transparent;border:1px solid var(--tmd-border)}
|
||||
|
||||
/* Approvals: one grouped queue; the destructive action is text, not a slab. */
|
||||
:host([data-tm-design="graphite"]) .ap-cp-item{background:transparent;border:0;border-radius:0;box-shadow:none}
|
||||
:host([data-tm-design="graphite"]) .ap-cp-item+.ap-cp-item{border-top:1px solid var(--tmd-border)}
|
||||
:host([data-tm-design="graphite"]) .ap-cp-title{font-weight:500;letter-spacing:-.18px}
|
||||
:host([data-tm-design="graphite"]) .ap-cp-sub{color:var(--tmd-dim);font-weight:400}
|
||||
:host([data-tm-design="graphite"]) .ap-cp-photo{border-radius:8px;border:1px solid var(--tmd-border)}
|
||||
:host([data-tm-design="graphite"]) .ap-gold-soft{background:transparent;color:var(--tmd-dim)}
|
||||
:host([data-tm-design="graphite"]) .approve-all-btn{background:transparent;color:#fff;border:1px solid rgba(255,255,255,.45);font-weight:500}
|
||||
|
||||
/* Leaderboard: rank, name, figure — right-aligned and tabular. */
|
||||
:host([data-tm-design="graphite"]) .lb-cp-row{background:transparent;border:0;border-radius:0}
|
||||
:host([data-tm-design="graphite"]) .lb-cp-row+.lb-cp-row{border-top:1px solid var(--tmd-border)}
|
||||
:host([data-tm-design="graphite"]) .lb-cp-rank{background:transparent;border:0;color:var(--tmd-dim);font-variant-numeric:tabular-nums;font-weight:500}
|
||||
:host([data-tm-design="graphite"]) .lb-cp-name{font-weight:500;letter-spacing:-.18px}
|
||||
:host([data-tm-design="graphite"]) .lb-cp-score{font-variant-numeric:tabular-nums;font-weight:600;letter-spacing:-.6px}
|
||||
:host([data-tm-design="graphite"]) .lb-cp-unit{color:var(--tmd-dim);font-weight:400}
|
||||
:host([data-tm-design="graphite"]) .lb-cp-row.win{background:transparent}
|
||||
:host([data-tm-design="graphite"]) .lb-cp-meta ha-icon{--mdc-icon-size:14px;vertical-align:-2px;margin-right:3px;color:var(--tmd-dim)}
|
||||
|
||||
/* Overview: hairline-separated stats, then a grouped per-child list. */
|
||||
:host([data-tm-design="graphite"]) .ov-kids{border:1px solid var(--tmd-border);border-radius:var(--tmd-radius-sm)}
|
||||
:host([data-tm-design="graphite"]) .ov-kid{background:transparent;border:0;border-radius:0}
|
||||
:host([data-tm-design="graphite"]) .ov-kid+.ov-kid{border-top:1px solid var(--tmd-border)}
|
||||
:host([data-tm-design="graphite"]) .ov-kid-flag{background:transparent;border:0;color:var(--tmd-dim);font-weight:400}
|
||||
:host([data-tm-design="graphite"]) .ov-alert{background:transparent;border:1px solid var(--tmd-border);color:var(--tmd-text)}
|
||||
:host([data-tm-design="graphite"]) .ov-today .val,
|
||||
:host([data-tm-design="graphite"]) .pts{font-variant-numeric:tabular-nums;font-weight:600;letter-spacing:-.6px}
|
||||
:host([data-tm-design="graphite"]) .nm,
|
||||
:host([data-tm-design="graphite"]) .ttl{font-weight:500;letter-spacing:-.18px}`;
|
||||
|
||||
if (!document.getElementById("taskmate-design-fonts")) {
|
||||
const styleEl = document.createElement("style");
|
||||
@@ -249,7 +421,14 @@
|
||||
el.setAttribute("data-tm-design", design);
|
||||
el.toggleAttribute("data-tm-dark", design !== "classic" && isDark(hass, el));
|
||||
}
|
||||
return design;
|
||||
// The host keeps the real id (its CSS keys off it); the card gets the
|
||||
// layout id so aliased styles reuse an existing render path.
|
||||
return LAYOUT_ALIAS[design] || design;
|
||||
}
|
||||
|
||||
/** The real design id stamped on a card host (not the layout alias). */
|
||||
function active(el) {
|
||||
return (el && el.getAttribute("data-tm-design")) || "classic";
|
||||
}
|
||||
|
||||
/** ha-form select options for a per-card design override (includes "use global"). */
|
||||
@@ -262,6 +441,7 @@
|
||||
{ value: "console", label: label("console", "Console") },
|
||||
{ value: "cleanpro", label: label("cleanpro", "Clean Pro") },
|
||||
{ value: "accessible", label: label("accessible", "Accessible") },
|
||||
{ value: "graphite", label: label("graphite", "Graphite") },
|
||||
];
|
||||
}
|
||||
|
||||
@@ -307,7 +487,7 @@
|
||||
`;
|
||||
}
|
||||
|
||||
window.__taskmate_design = { IDS, resolve, isDark, apply, editorOptions, styles, cssText, tokensCSS: TOKENS, colourPicker };
|
||||
window.__taskmate_design = { IDS, resolve, isDark, apply, active, editorOptions, styles, cssText, tokensCSS: TOKENS, colourPicker };
|
||||
|
||||
/**
|
||||
* The single place that decides what a chore looks like (#750).
|
||||
|
||||
@@ -659,10 +659,17 @@ class TaskMateLeaderboardCard extends LitElement {
|
||||
_lbMetaChips(r, ctx, cls) {
|
||||
const sortBy = ctx.sortBy;
|
||||
const chips = [];
|
||||
if (this.config.show_streak !== false && sortBy !== "streak") chips.push(html`<span>🔥 ${r.streak}</span>`);
|
||||
if (this.config.show_weekly !== false && sortBy !== "weekly") chips.push(html`<span>📅 ${r.weekly}</span>`);
|
||||
if (sortBy !== "points") chips.push(html`<span>⭐ ${r.points}</span>`);
|
||||
if (this.config.show_career !== false && sortBy !== "career") chips.push(html`<span>🏆 ${r.career}</span>`);
|
||||
// Graphite carries no emoji chrome, so the same figures get monoline
|
||||
// icons there. active() returns the real design id (apply() hands the
|
||||
// render path the layout alias, which for graphite is "cleanpro").
|
||||
const mono = window.__taskmate_design?.active?.(this) === "graphite";
|
||||
const stat = (icon, emoji, value) => (mono
|
||||
? html`<span><ha-icon icon="${icon}"></ha-icon>${value}</span>`
|
||||
: html`<span>${emoji} ${value}</span>`);
|
||||
if (this.config.show_streak !== false && sortBy !== "streak") chips.push(stat("mdi:fire", "🔥", r.streak));
|
||||
if (this.config.show_weekly !== false && sortBy !== "weekly") chips.push(stat("mdi:calendar-blank-outline", "📅", r.weekly));
|
||||
if (sortBy !== "points") chips.push(stat("mdi:star-outline", "⭐", r.points));
|
||||
if (this.config.show_career !== false && sortBy !== "career") chips.push(stat("mdi:trophy-outline", "🏆", r.career));
|
||||
if (!chips.length) return "";
|
||||
return html`<div class="row muted ${cls}">${chips}</div>`;
|
||||
}
|
||||
|
||||
@@ -581,6 +581,12 @@ class TaskMateOverviewCard extends LitElement {
|
||||
const perChild = availability[c.id];
|
||||
if (perChild && perChild[child.id] === false) return false;
|
||||
}
|
||||
// Weekly target (#883): the week's quota is filled, so the chore isn't
|
||||
// outstanding today. Filtering here rather than at the two call sites
|
||||
// keeps it out of the progress ring as well — otherwise the ring could
|
||||
// never reach 100% once a weekly chore was finished for the week.
|
||||
const weeklyTarget = Number(c.weekly_target) || 0;
|
||||
if (weeklyTarget > 0 && Number((child.weekly_chore_progress || {})[c.id] || 0) >= weeklyTarget) return false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -345,6 +345,8 @@ class TaskMatePanel extends HTMLElement {
|
||||
['Pool refund (reward sold out):', 'activity.reason_pool_refund_sold_out'],
|
||||
['Pool refund (reward cost reduced):', 'activity.reason_pool_refund_cost_reduced'],
|
||||
['Pool refund (reward deleted):', 'activity.reason_pool_refund_deleted'],
|
||||
['Pool refund (reward funding changed):', 'activity.reason_pool_refund_funding_changed'],
|
||||
['Pool refund (reward assignment changed):', 'activity.reason_pool_refund_assignment_changed'],
|
||||
['Penalty:', 'activity.reason_penalty'],
|
||||
['Bonus:', 'activity.reason_bonus'],
|
||||
];
|
||||
@@ -1417,6 +1419,7 @@ class TaskMatePanel extends HTMLElement {
|
||||
timed_rate_points: 10, timed_rate_minutes: 5, timed_max_daily_minutes: 0,
|
||||
assigned_to: [], requires_approval: true,
|
||||
time_category: "anytime", completion_sound: "coin", daily_limit: 1,
|
||||
weekly_target: 0,
|
||||
difficulty: "medium",
|
||||
claim_allowance_minutes: 0,
|
||||
schedule_mode: "specific_days",
|
||||
@@ -1657,6 +1660,7 @@ class TaskMatePanel extends HTMLElement {
|
||||
completion_sound: d.completion_sound || "coin",
|
||||
difficulty: d.difficulty || "medium",
|
||||
daily_limit: Number(d.daily_limit) || 1,
|
||||
weekly_target: Math.max(0, Number(d.weekly_target) || 0),
|
||||
schedule_mode: d.schedule_mode || "specific_days",
|
||||
due_days: d.due_days || [],
|
||||
recurrence: d.recurrence || "weekly",
|
||||
@@ -3235,7 +3239,7 @@ class TaskMatePanel extends HTMLElement {
|
||||
ts: c.approved_at || c.claimed_at, kind: "claim",
|
||||
child: (childById[c.child_id] || {}).name || "?",
|
||||
label: `${this._t("panel.activity_claimed_reward", {name: (rewardById[c.reward_id] || {}).name || this._t("panel.activity_deleted_reward")})}`,
|
||||
points: -((rewardById[c.reward_id] || {}).cost || 0),
|
||||
points: -(c.approved_cost ?? (rewardById[c.reward_id] || {}).cost ?? 0),
|
||||
}));
|
||||
transactions.forEach(t => events.push({
|
||||
ts: t.created_at, kind: "manual",
|
||||
@@ -3283,6 +3287,10 @@ class TaskMatePanel extends HTMLElement {
|
||||
chorePoints = Math.floor(c.timed_duration_seconds / rateSeconds) * (chore.timed_rate_points || 0);
|
||||
}
|
||||
}
|
||||
// What the child was promised when they submitted beats anything
|
||||
// recalculated here: a chore edited while the work sat in the
|
||||
// queue, or a speed/roulette bonus that no longer applies.
|
||||
chorePoints = c.submitted_points ?? chorePoints;
|
||||
const photoCap = [choreName, (child && child.name) || "", c.completed_at ? new Date(c.completed_at).toLocaleString([], { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" }) : ""].filter(Boolean).join(" · ");
|
||||
return `
|
||||
<div class="tm-approval-item">
|
||||
@@ -3464,6 +3472,11 @@ class TaskMatePanel extends HTMLElement {
|
||||
? this._t("panel.common_one_shot")
|
||||
: ((c.due_days || []).length === 0 ? this._t("panel.common_daily") : (c.due_days || []).map(d => this._labelOf(DAYS, d)).join(" · "));
|
||||
const schedClass = c.schedule_mode === "recurring" ? "tm-pill-accent" : c.schedule_mode === "one_shot" ? "tm-pill-warn" : "tm-pill-success";
|
||||
// A weekly quota doesn't replace the day list — the chore is still only
|
||||
// offered on those days, it just stops once the week's count is filled.
|
||||
const weeklyPill = Number(c.weekly_target) > 0
|
||||
? ` <span class="tm-pill tm-pill-accent">${this._t("panel.chore_weekly_target_pill", { count: Number(c.weekly_target) })}</span>`
|
||||
: "";
|
||||
const modeBadge = c.assignment_mode && c.assignment_mode !== "everyone"
|
||||
? `<span class="tm-pill tm-pill-${this._esc(c.assignment_mode)}">${this._t(`panel.assign_${c.assignment_mode}_short`)}</span>` : "";
|
||||
const nameCell = renaming
|
||||
@@ -3485,7 +3498,7 @@ class TaskMatePanel extends HTMLElement {
|
||||
<td><span class="tm-pill">${this._esc(this._timeCategoryLabel(c.time_category))}</span></td>
|
||||
<td>${assignedNames} ${modeBadge}</td>
|
||||
<td>${currentName}</td>
|
||||
<td><span class="tm-pill ${schedClass} tm-pill-dot">${this._esc(schedLabel)}</span></td>
|
||||
<td><span class="tm-pill ${schedClass} tm-pill-dot">${this._esc(schedLabel)}</span>${weeklyPill}</td>
|
||||
<td>${c.requires_approval ? `<span class='tm-yes'>${this._t("panel.common_yes")}</span>` : `<span class='tm-no'>${this._t("panel.common_no")}</span>`}</td>
|
||||
</tr>
|
||||
`;
|
||||
@@ -4539,7 +4552,7 @@ class TaskMatePanel extends HTMLElement {
|
||||
<div class="tm-setting-row">
|
||||
<div class="tm-setting-label">${this._t("panel.settings_card_design_label")}<small>${this._t("panel.settings_card_design_hint")}</small></div>
|
||||
<select class="tm-select" data-setting="card_design">
|
||||
${["classic","playroom","console","cleanpro"].map(v => `<option value="${v}" ${v === (s.card_design || "classic") ? "selected" : ""}>${this._esc(this._t("common.design." + v))}</option>`).join("")}
|
||||
${["classic","playroom","console","cleanpro","accessible","graphite"].map(v => `<option value="${v}" ${v === (s.card_design || "classic") ? "selected" : ""}>${this._esc(this._t("common.design." + v))}</option>`).join("")}
|
||||
</select>
|
||||
</div>
|
||||
<div class="tm-setting-row">
|
||||
@@ -5433,6 +5446,8 @@ class TaskMatePanel extends HTMLElement {
|
||||
`).join("")}
|
||||
</div>
|
||||
</div>
|
||||
${this._field(this._t("panel.chore_weekly_target_label"), "weekly_target", d.weekly_target || 0, "number",
|
||||
this._t("panel.chore_weekly_target_hint"))}
|
||||
` : "",
|
||||
showRecurring ? `
|
||||
<div class="tm-field-row">
|
||||
|
||||
@@ -1042,6 +1042,10 @@ class TaskMateParentDashboardCard extends LitElement {
|
||||
if (c.schedule_mode === 'recurring') {
|
||||
if (perChild && perChild[child.id] === false) return false;
|
||||
}
|
||||
// Weekly target (#883): quota filled for the week, so it is neither
|
||||
// outstanding nor part of today's total.
|
||||
const weeklyTarget = Number(c.weekly_target) || 0;
|
||||
if (weeklyTarget > 0 && Number((child.weekly_chore_progress || {})[c.id] || 0) >= weeklyTarget) return false;
|
||||
return true;
|
||||
});
|
||||
const childChoreIds = new Set(childChores.map(c => c.id));
|
||||
|
||||
Reference in New Issue
Block a user