147 files
This commit is contained in:
@@ -208,6 +208,8 @@
|
||||
"child.chores_resetting_soon": "Aufgaben werden bald zurückgesetzt!",
|
||||
"child.click_to_complete": "Klicken Sie hier, um den Vorgang abzuschließen",
|
||||
"child.click_to_undo": "Klicken Sie zum Rückgängigmachen",
|
||||
"child.claimed_by_another": "Bereits erledigt von {name}",
|
||||
"child.claimed_by_another_generic": "Heute bereits beansprucht",
|
||||
"child.difficulty_easy": "Leicht",
|
||||
"child.difficulty_medium": "Mittel",
|
||||
"child.difficulty_hard": "Schwer",
|
||||
@@ -236,10 +238,16 @@
|
||||
"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.parent": "Eltern",
|
||||
"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",
|
||||
"child.editor.due_days_show": "Anzeigen – trotzdem normal anzeigen",
|
||||
"child.editor.first_come_dim": "Gedimmt – für alle außer dem Kind, das die Aufgabe erledigt hat, ausgegraut anzeigen",
|
||||
"child.editor.first_come_helper": "Was für den Rest der Gruppe angezeigt wird, nachdem eine Wer-zuerst-kommt-Aufgabe von einem Kind beansprucht wurde",
|
||||
"child.editor.first_come_hide": "Ausblenden – nach Beanspruchung entfernen (Standard, entspricht dem bisherigen Verhalten)",
|
||||
"child.editor.first_come_show": "Anzeigen – für alle vollständig sichtbar lassen (weiterhin nicht antippbar, sobald beansprucht)",
|
||||
"child.editor.first_come_when_completed": "Wer-zuerst-kommt-Aufgaben – nach Beanspruchung",
|
||||
"child.editor.elapsed_dim": "Gedimmt – ausgegraut anzeigen, nicht interaktiv",
|
||||
"child.editor.elapsed_helper": "Was ist zu zeigen, wenn eine Morgen-/Nachmittags-/Abend-/Nachtaufgabe nicht erledigt wurde und die Zeit abgelaufen ist?",
|
||||
"child.editor.elapsed_hide": "Ausblenden – nicht anzeigen",
|
||||
|
||||
@@ -210,6 +210,8 @@
|
||||
"child.chores_resetting_soon": "Chores resetting soon!",
|
||||
"child.click_to_complete": "Click to complete",
|
||||
"child.click_to_undo": "Click to undo",
|
||||
"child.claimed_by_another": "Already done by {name}",
|
||||
"child.claimed_by_another_generic": "Already claimed today",
|
||||
"child.difficulty_easy": "Easy",
|
||||
"child.difficulty_medium": "Medium",
|
||||
"child.difficulty_hard": "Hard",
|
||||
@@ -238,10 +240,16 @@
|
||||
"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.parent": "Parent",
|
||||
"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",
|
||||
"child.editor.due_days_show": "Show — show normally regardless",
|
||||
"child.editor.first_come_dim": "Dim — show greyed out for everyone except the child who completed it",
|
||||
"child.editor.first_come_helper": "What to show to the rest of the pool once a first-come chore has been claimed by one child",
|
||||
"child.editor.first_come_hide": "Hide — remove once claimed (default, matches prior behavior)",
|
||||
"child.editor.first_come_show": "Show — keep fully visible for everyone (still not tappable once claimed)",
|
||||
"child.editor.first_come_when_completed": "First-Come Chores — When Claimed",
|
||||
"child.editor.elapsed_dim": "Dim — show greyed out, non-interactive",
|
||||
"child.editor.elapsed_helper": "What to show when a morning/afternoon/evening/night chore wasn't completed and that time has passed",
|
||||
"child.editor.elapsed_hide": "Hide — don't show",
|
||||
|
||||
@@ -210,6 +210,8 @@
|
||||
"child.chores_resetting_soon": "Chores resetting soon!",
|
||||
"child.click_to_complete": "Click to complete",
|
||||
"child.click_to_undo": "Click to undo",
|
||||
"child.claimed_by_another": "Already done by {name}",
|
||||
"child.claimed_by_another_generic": "Already claimed today",
|
||||
"child.difficulty_easy": "Easy",
|
||||
"child.difficulty_medium": "Medium",
|
||||
"child.difficulty_hard": "Hard",
|
||||
@@ -238,10 +240,16 @@
|
||||
"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.parent": "Parent",
|
||||
"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",
|
||||
"child.editor.due_days_show": "Show — show normally regardless",
|
||||
"child.editor.first_come_dim": "Dim — show greyed out for everyone except the child who completed it",
|
||||
"child.editor.first_come_helper": "What to show to the rest of the pool once a first-come chore has been claimed by one child",
|
||||
"child.editor.first_come_hide": "Hide — remove once claimed (default, matches prior behavior)",
|
||||
"child.editor.first_come_show": "Show — keep fully visible for everyone (still not tappable once claimed)",
|
||||
"child.editor.first_come_when_completed": "First-Come Chores — When Claimed",
|
||||
"child.editor.elapsed_dim": "Dim — show greyed out, non-interactive",
|
||||
"child.editor.elapsed_helper": "What to show when a morning/afternoon/evening/night chore wasn't completed and that time has passed",
|
||||
"child.editor.elapsed_hide": "Hide — don't show",
|
||||
|
||||
@@ -208,6 +208,8 @@
|
||||
"child.chores_resetting_soon": "Réinitialisation des tâches bientôt !",
|
||||
"child.click_to_complete": "Cliquez pour terminer",
|
||||
"child.click_to_undo": "Cliquez pour annuler",
|
||||
"child.claimed_by_another": "Déjà fait par {name}",
|
||||
"child.claimed_by_another_generic": "Déjà prise aujourd'hui",
|
||||
"child.difficulty_easy": "Facile",
|
||||
"child.difficulty_medium": "Moyen",
|
||||
"child.difficulty_hard": "Difficile",
|
||||
@@ -236,10 +238,16 @@
|
||||
"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.parent": "Parent",
|
||||
"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",
|
||||
"child.editor.due_days_show": "Afficher — afficher normalement malgré tout",
|
||||
"child.editor.first_come_dim": "Estomper — afficher en gris pour tout le monde sauf l'enfant qui a terminé la tâche",
|
||||
"child.editor.first_come_helper": "Ce qu'il faut montrer au reste du groupe une fois qu'une tâche de type premier arrivé a été prise par un enfant",
|
||||
"child.editor.first_come_hide": "Masquer — retirer une fois prise (par défaut, comportement identique à avant)",
|
||||
"child.editor.first_come_show": "Afficher — rester entièrement visible pour tous (toujours non cliquable une fois prise)",
|
||||
"child.editor.first_come_when_completed": "Tâches premier arrivé — une fois prises",
|
||||
"child.editor.elapsed_dim": "Estomper — afficher en gris, non interactif",
|
||||
"child.editor.elapsed_helper": "Que montrer lorsqu'une tâche du matin/après-midi/soir/nuit n'a pas été accomplie et que le moment est passé",
|
||||
"child.editor.elapsed_hide": "Masquer — ne pas afficher",
|
||||
|
||||
@@ -208,6 +208,8 @@
|
||||
"child.chores_resetting_soon": "Oppgavene nullstilles snart!",
|
||||
"child.click_to_complete": "Klikk for å fullføre",
|
||||
"child.click_to_undo": "Klikk for å angre",
|
||||
"child.claimed_by_another": "Allerede gjort av {name}",
|
||||
"child.claimed_by_another_generic": "Allerede tatt i dag",
|
||||
"child.difficulty_easy": "Lett",
|
||||
"child.difficulty_medium": "Middels",
|
||||
"child.difficulty_hard": "Vanskelig",
|
||||
@@ -236,10 +238,16 @@
|
||||
"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.parent": "Forelder",
|
||||
"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",
|
||||
"child.editor.due_days_show": "Vis — vis normalt uansett",
|
||||
"child.editor.first_come_dim": "Dim — vis gråtonet for alle unntatt barnet som fullførte oppgaven",
|
||||
"child.editor.first_come_helper": "Hva som vises for resten av gruppen når en førstemann-oppgave er tatt av ett barn",
|
||||
"child.editor.first_come_hide": "Skjul — fjern når den er tatt (standard, samme som tidligere oppførsel)",
|
||||
"child.editor.first_come_show": "Vis — hold den fullt synlig for alle (fortsatt ikke trykkbar når den er tatt)",
|
||||
"child.editor.first_come_when_completed": "Førstemann-oppgaver — når tatt",
|
||||
"child.editor.elapsed_dim": "Dim — vis gråtonet, ikke-interaktiv",
|
||||
"child.editor.elapsed_helper": "Hva som vises når en morgen/ettermiddag/kveld/natt-oppgave ikke ble fullført og tiden har gått",
|
||||
"child.editor.elapsed_hide": "Skjul — ikke vis",
|
||||
|
||||
@@ -208,6 +208,8 @@
|
||||
"child.chores_resetting_soon": "Oppgåvene nullstillast snart!",
|
||||
"child.click_to_complete": "Klikk for å fullføre",
|
||||
"child.click_to_undo": "Klikk for å angre",
|
||||
"child.claimed_by_another": "Allereie gjort av {name}",
|
||||
"child.claimed_by_another_generic": "Allereie teken i dag",
|
||||
"child.difficulty_easy": "Lett",
|
||||
"child.difficulty_medium": "Middels",
|
||||
"child.difficulty_hard": "Vanskeleg",
|
||||
@@ -236,10 +238,16 @@
|
||||
"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.parent": "Forelder",
|
||||
"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",
|
||||
"child.editor.due_days_show": "Vis — vis normalt uansett",
|
||||
"child.editor.first_come_dim": "Dim — vis gråtona for alle unnateke barnet som fullførte oppgåva",
|
||||
"child.editor.first_come_helper": "Kva som visast for resten av gruppa når ei førstemann-oppgåve er teken av eitt barn",
|
||||
"child.editor.first_come_hide": "Gøym — fjern når ho er teken (standard, same som tidlegare åtferd)",
|
||||
"child.editor.first_come_show": "Vis — hald ho fullt synleg for alle (framleis ikkje trykkbar når ho er teken)",
|
||||
"child.editor.first_come_when_completed": "Førstemann-oppgåver — når teken",
|
||||
"child.editor.elapsed_dim": "Dim — vis gråtona, ikkje-interaktiv",
|
||||
"child.editor.elapsed_helper": "Kva som visast når ei morgon/ettermiddag/kveld/natt-oppgåve ikkje vart fullført og tida har gått",
|
||||
"child.editor.elapsed_hide": "Gøym — ikkje vis",
|
||||
|
||||
@@ -208,6 +208,8 @@
|
||||
"child.chores_resetting_soon": "As tarefas reiniciam em breve!",
|
||||
"child.click_to_complete": "Clica para concluir",
|
||||
"child.click_to_undo": "Clica para desfazer",
|
||||
"child.claimed_by_another": "Já feita por {name}",
|
||||
"child.claimed_by_another_generic": "Já reivindicada hoje",
|
||||
"child.difficulty_easy": "Fácil",
|
||||
"child.difficulty_medium": "Médio",
|
||||
"child.difficulty_hard": "Difícil",
|
||||
@@ -236,10 +238,16 @@
|
||||
"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.parent": "Responsável",
|
||||
"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",
|
||||
"child.editor.due_days_show": "Mostrar — mostrar normalmente",
|
||||
"child.editor.first_come_dim": "Atenuar — mostrar em cinza para todos, exceto a criança que concluiu a tarefa",
|
||||
"child.editor.first_come_helper": "O que mostrar para o resto do grupo depois que uma tarefa do tipo quem chega primeiro for reivindicada por uma criança",
|
||||
"child.editor.first_come_hide": "Ocultar — remover assim que reivindicada (padrão, igual ao comportamento anterior)",
|
||||
"child.editor.first_come_show": "Mostrar — manter totalmente visível para todos (continua sem toque depois de reivindicada)",
|
||||
"child.editor.first_come_when_completed": "Tarefas Quem Chega Primeiro — Quando Reivindicadas",
|
||||
"child.editor.elapsed_dim": "Atenuar — mostrar em cinzento, sem interação",
|
||||
"child.editor.elapsed_helper": "O que mostrar quando uma tarefa da manhã/tarde/noite/madrugada não foi concluída e o horário já passou",
|
||||
"child.editor.elapsed_hide": "Ocultar — não mostrar",
|
||||
|
||||
@@ -208,6 +208,8 @@
|
||||
"child.chores_resetting_soon": "As tarefas reiniciam em breve!",
|
||||
"child.click_to_complete": "Clica para concluir",
|
||||
"child.click_to_undo": "Clica para desfazer",
|
||||
"child.claimed_by_another": "Já feita por {name}",
|
||||
"child.claimed_by_another_generic": "Já reclamada hoje",
|
||||
"child.difficulty_easy": "Fácil",
|
||||
"child.difficulty_medium": "Médio",
|
||||
"child.difficulty_hard": "Difícil",
|
||||
@@ -236,10 +238,16 @@
|
||||
"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.parent": "Responsável",
|
||||
"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",
|
||||
"child.editor.due_days_show": "Mostrar — mostrar normalmente",
|
||||
"child.editor.first_come_dim": "Atenuar — mostrar em cinzento para todos exceto a criança que concluiu a tarefa",
|
||||
"child.editor.first_come_helper": "O que mostrar ao resto do grupo depois de uma tarefa do tipo quem chega primeiro ser reclamada por uma criança",
|
||||
"child.editor.first_come_hide": "Ocultar — remover assim que reclamada (predefinição, igual ao comportamento anterior)",
|
||||
"child.editor.first_come_show": "Mostrar — manter totalmente visível para todos (continua sem toque depois de reclamada)",
|
||||
"child.editor.first_come_when_completed": "Tarefas Quem Chega Primeiro — Quando Reclamadas",
|
||||
"child.editor.elapsed_dim": "Atenuar — mostrar em cinzento, sem interação",
|
||||
"child.editor.elapsed_helper": "O que mostrar quando uma tarefa da manhã/tarde/noite/madrugada não foi concluída e o horário já passou",
|
||||
"child.editor.elapsed_hide": "Ocultar — não mostrar",
|
||||
|
||||
@@ -1118,6 +1118,29 @@ class TaskMateChildCard extends LitElement {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* First-come chore claimed by a sibling today — dimmed/greyed, same
|
||||
treatment as recurrence-unavailable (first_come_claimed_mode: dim) */
|
||||
.chore-card.first-come-unavailable {
|
||||
opacity: 0.45;
|
||||
filter: grayscale(0.6);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.first-come-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
color: var(--secondary-text-color);
|
||||
background: color-mix(in srgb, var(--primary-text-color, #212121) 8%, transparent);
|
||||
border-radius: 8px;
|
||||
padding: 1px 6px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.first-come-label ha-icon { --mdc-icon-size: 12px; }
|
||||
|
||||
/* Locked preview — chore is visible but not yet claimable because its
|
||||
time-of-day window has not started. Dim with a padlock badge. */
|
||||
.chore-card.chore-locked {
|
||||
@@ -2593,9 +2616,18 @@ class TaskMateChildCard extends LitElement {
|
||||
|
||||
/** Mirror of _renderChoreCard's "completed today" detection, designed branch only. */
|
||||
_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
|
||||
// child. Every other assignment mode treats that as "done for whoever is
|
||||
// looking", which is the intended behavior. first_come is exclusive: a
|
||||
// pool-wide dismissal means nobody actually completed it, so it must not
|
||||
// read as this child's own done state (green row + live undo chip) for
|
||||
// anyone — it should fall through to the locked/dimmed row instead
|
||||
// (chore._isFirstComeLocked, set in _filterAndSortChores).
|
||||
const isFirstCome = chore.assignment_mode === "first_come";
|
||||
const childCompletionsToday = (todaysCompletions || []).filter(
|
||||
(comp) => comp.chore_id === chore.id
|
||||
&& (comp.child_id === child.id || comp.child_id === "__parent__")
|
||||
&& (comp.child_id === child.id || (!isFirstCome && comp.child_id === "__parent__"))
|
||||
&& !comp.bonus_subtask_id
|
||||
);
|
||||
let count = childCompletionsToday.length;
|
||||
@@ -2608,7 +2640,11 @@ class TaskMateChildCard extends LitElement {
|
||||
if (!actualTs.some(a => Math.abs(a - t) < 2000)) count += 1;
|
||||
}
|
||||
}
|
||||
const done = count >= (chore.daily_limit || 1);
|
||||
// first_come is a single-winner race whatever quota is stored on the chore —
|
||||
// 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));
|
||||
return { done, completions: childCompletionsToday };
|
||||
}
|
||||
|
||||
@@ -2651,6 +2687,7 @@ class TaskMateChildCard extends LitElement {
|
||||
const elapsedTimeMode = this.config.elapsed_time_mode || "dim";
|
||||
const dependencyMode = this.config.dependency_mode || "hide";
|
||||
const recurrenceDoneMode = this.config.recurrence_done_mode || "dim";
|
||||
const firstComeClaimedMode = this.config.first_come_claimed_mode || "hide";
|
||||
|
||||
// Annotate each chore with done-state + handlers, preserving the classic path's logic.
|
||||
const rows = childChores.map((chore, i) => {
|
||||
@@ -2662,8 +2699,12 @@ class TaskMateChildCard extends LitElement {
|
||||
// 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;
|
||||
// First-come chore claimed by a different pool member (first_come_claimed_mode
|
||||
// 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;
|
||||
const onAct = () => {
|
||||
if (loading || blocked || recLocked) return;
|
||||
if (loading || blocked || recLocked || firstComeLocked) return;
|
||||
if (done) this._handleUndo(chore, child, completions);
|
||||
else this._handleComplete(chore, child);
|
||||
};
|
||||
@@ -2675,10 +2716,11 @@ class TaskMateChildCard extends LitElement {
|
||||
(chore._isTimeElapsed && elapsedTimeMode === "dim") ||
|
||||
(blocked && dependencyMode === "dim") ||
|
||||
(chore._isRecurrenceLocked && recurrenceDoneMode === "dim") ||
|
||||
(chore._isFirstComeLocked && firstComeClaimedMode === "dim") ||
|
||||
chore._isLockedPreview === true
|
||||
);
|
||||
return {
|
||||
chore, child, done, loading, onAct, index: i, dimmed, blocked, recLocked,
|
||||
chore, child, done, loading, onAct, index: i, dimmed, blocked, recLocked, firstComeLocked,
|
||||
tone: this._designTone(i),
|
||||
glyph: this._choreGlyph(chore),
|
||||
points: chore.effective_points ?? chore.points,
|
||||
@@ -2816,13 +2858,21 @@ class TaskMateChildCard extends LitElement {
|
||||
const recLabel = r.recLocked
|
||||
? (r.chore.recurrence ? r.chore.recurrence.replace(/_/g, " ") : this._t("child.recurring"))
|
||||
: "";
|
||||
// Same idea as recLabel, but for a first-come chore a sibling already
|
||||
// claimed today — name it when we know who, otherwise a generic label.
|
||||
const firstComeLabel = r.firstComeLocked
|
||||
? (r.chore._firstComeCompletedByName
|
||||
? this._t("child.claimed_by_another", { name: r.chore._firstComeCompletedByName })
|
||||
: this._t("child.claimed_by_another_generic"))
|
||||
: "";
|
||||
return html`
|
||||
${r.mandatory || r.photo || depNames.length || recLabel ? html`
|
||||
${r.mandatory || r.photo || depNames.length || recLabel || firstComeLabel ? 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>` : ""}
|
||||
${firstComeLabel ? html`<span class="tmd-tag">✅ ${firstComeLabel}</span>` : ""}
|
||||
</div>` : ""}
|
||||
${showDesc ? html`<div class="tmd-desc">${r.chore.description}</div>` : ""}`;
|
||||
}
|
||||
@@ -2838,7 +2888,7 @@ class TaskMateChildCard extends LitElement {
|
||||
|
||||
_designDoneBtn(r, label, cls) {
|
||||
return html`<button class="btn ${cls || ""}"
|
||||
?disabled=${r.loading || r.blocked || r.recLocked}
|
||||
?disabled=${r.loading || r.blocked || r.recLocked || r.firstComeLocked}
|
||||
@click=${(e) => { e.stopPropagation(); r.onAct(); }}>${label}</button>`;
|
||||
}
|
||||
|
||||
@@ -3009,6 +3059,7 @@ class TaskMateChildCard extends LitElement {
|
||||
// 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 firstComeClaimedMode = this.config.first_come_claimed_mode || 'hide';
|
||||
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,
|
||||
@@ -3083,7 +3134,35 @@ class TaskMateChildCard extends LitElement {
|
||||
);
|
||||
const hasPendingBonus = bonusSubtasks.some(b => b && b.id && !completedBonusIds.has(b.id));
|
||||
if (!hasPendingBonus) {
|
||||
isAssignedToChild = false;
|
||||
// first_come_claimed_mode (#first-come-visibility): by default a
|
||||
// claimed first-come chore disappears for the whole pool,
|
||||
// winner included — unchanged from prior behaviour. Under
|
||||
// dim/show it stays in the list instead: the winner's own row
|
||||
// still renders as done via _isChoreDone below, and every
|
||||
// other pool member is flagged locked so the render layer can
|
||||
// grey it out and refuse taps (the backend rejects the
|
||||
// completion anyway).
|
||||
if (assignmentMode === 'first_come' && firstComeClaimedMode !== 'hide') {
|
||||
const completedByThisChild = allTodayCompletions.some(
|
||||
(comp) => comp.chore_id === chore.id
|
||||
&& String(comp.child_id) === childId
|
||||
&& !comp.bonus_subtask_id
|
||||
);
|
||||
chore._isFirstComeLocked = !completedByThisChild;
|
||||
if (chore._isFirstComeLocked) {
|
||||
const winningCompletion = allTodayCompletions.find(
|
||||
(comp) => comp.chore_id === chore.id && !comp.bonus_subtask_id
|
||||
);
|
||||
const winner = winningCompletion
|
||||
? (attrs.children || []).find(c => String(c.id) === String(winningCompletion.child_id))
|
||||
: null;
|
||||
chore._firstComeCompletedByName = winner
|
||||
? winner.name
|
||||
: (winningCompletion && winningCompletion.child_id === '__parent__' ? this._t('child.parent') : '');
|
||||
}
|
||||
} else {
|
||||
isAssignedToChild = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3678,12 +3757,23 @@ class TaskMateChildCard extends LitElement {
|
||||
const isCelebrating = this._celebrating === chore.id;
|
||||
|
||||
// Check how many times this chore was completed today by this child
|
||||
// Both pending (awaiting approval) AND approved completions count toward the daily limit
|
||||
// Both pending (awaiting approval) AND approved completions count toward the daily limit.
|
||||
// Exception: async_parent_complete_chore's child_id="__parent__" dismisses a
|
||||
// first_come chore for the whole pool with zero points and no winner — it must
|
||||
// not read as THIS child's own done state (green row + live undo chip) for
|
||||
// anyone, or every sibling gets a completed chip for a chore none of them did.
|
||||
// It should fall through to the locked/dimmed row instead (chore._isFirstComeLocked
|
||||
// below). Every other assignment mode keeps the existing __parent__-counts-for-
|
||||
// everyone behavior, which is intentional there.
|
||||
const isFirstCome = chore.assignment_mode === "first_come";
|
||||
const childCompletionsToday = todaysCompletions.filter(
|
||||
(comp) => comp.chore_id === chore.id && (comp.child_id === child.id || comp.child_id === "__parent__") && !comp.bonus_subtask_id
|
||||
(comp) => comp.chore_id === chore.id && (comp.child_id === child.id || (!isFirstCome && comp.child_id === "__parent__")) && !comp.bonus_subtask_id
|
||||
);
|
||||
let completionsToday = childCompletionsToday.length;
|
||||
const dailyLimit = chore.daily_limit || 1;
|
||||
// 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);
|
||||
|
||||
// Check for optimistic completions (chores just completed but not yet confirmed by HA)
|
||||
const optimisticKey = `${chore.id}_${child.id}`;
|
||||
@@ -3730,6 +3820,12 @@ class TaskMateChildCard extends LitElement {
|
||||
// mode-gated. Same split dependency_mode uses.
|
||||
const recurrenceLocked = !!chore._isRecurrenceLocked && !isCompletedForToday;
|
||||
const notAvailableRecurrence = recurrenceLocked && recurrenceDoneMode === 'dim';
|
||||
// First-come chore claimed by a sibling today (first_come_claimed_mode
|
||||
// dim/show — hide is filtered upstream). Never true on the winner's own
|
||||
// row: _isFirstComeLocked is only set for the other pool members.
|
||||
const firstComeClaimedMode = this.config.first_come_claimed_mode || 'hide';
|
||||
const firstComeLocked = !!chore._isFirstComeLocked && !isCompletedForToday;
|
||||
const notAvailableFirstCome = firstComeLocked && firstComeClaimedMode === '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';
|
||||
@@ -3752,13 +3848,14 @@ class TaskMateChildCard extends LitElement {
|
||||
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 (firstComeLocked) return; // Claimed by another child today — not interactive
|
||||
if (isCompletedForToday) {
|
||||
this._handleUndo(chore, child, childCompletionsToday);
|
||||
} else {
|
||||
this._handleComplete(chore, child);
|
||||
}
|
||||
};
|
||||
const isInteractive = !(isLoading || notDueToday || recurrenceLocked || isLockedPreview || depBlocked || timeElapsed);
|
||||
const isInteractive = !(isLoading || notDueToday || recurrenceLocked || isLockedPreview || depBlocked || timeElapsed || firstComeLocked);
|
||||
const handleRowKeyDown = (e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
@@ -3774,13 +3871,17 @@ class TaskMateChildCard extends LitElement {
|
||||
? this._t('child.not_scheduled_for_today')
|
||||
: timeElapsed
|
||||
? this._t('child.time_has_passed')
|
||||
: firstComeLocked
|
||||
? (chore._firstComeCompletedByName
|
||||
? this._t('child.claimed_by_another', { name: chore._firstComeCompletedByName })
|
||||
: this._t('child.claimed_by_another_generic'))
|
||||
: 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" : ""} ${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 ? "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'}"
|
||||
@@ -3812,6 +3913,14 @@ class TaskMateChildCard extends LitElement {
|
||||
${chore.recurrence ? chore.recurrence.replace(/_/g, ' ') : this._t('child.recurring')}
|
||||
</div>
|
||||
` : ''}
|
||||
${firstComeLocked ? html`
|
||||
<div class="first-come-label">
|
||||
<ha-icon icon="mdi:check-circle-outline"></ha-icon>
|
||||
${chore._firstComeCompletedByName
|
||||
? this._t('child.claimed_by_another', { name: chore._firstComeCompletedByName })
|
||||
: this._t('child.claimed_by_another_generic')}
|
||||
</div>
|
||||
` : ''}
|
||||
<div class="chore-points">
|
||||
<ha-icon icon="${pointsIcon}"></ha-icon>
|
||||
+${chore.effective_points ?? chore.points}
|
||||
@@ -4829,6 +4938,19 @@ class TaskMateChildCardEditor extends LitElement {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'first_come_claimed_mode',
|
||||
selector: {
|
||||
select: {
|
||||
options: [
|
||||
{ value: 'hide', label: this._t('child.editor.first_come_hide') },
|
||||
{ value: 'dim', label: this._t('child.editor.first_come_dim') },
|
||||
{ value: 'show', label: this._t('child.editor.first_come_show') },
|
||||
],
|
||||
mode: 'dropdown',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'dependency_mode',
|
||||
selector: {
|
||||
@@ -4872,6 +4994,7 @@ 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'),
|
||||
first_come_claimed_mode: this._t('child.editor.first_come_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'),
|
||||
@@ -4891,6 +5014,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'),
|
||||
first_come_claimed_mode: this._t('child.editor.first_come_helper'),
|
||||
dependency_mode: this._t('child.editor.dependency_helper'),
|
||||
elapsed_time_mode: this._t('child.editor.elapsed_helper'),
|
||||
};
|
||||
@@ -4907,6 +5031,7 @@ 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',
|
||||
first_come_claimed_mode: this.config.first_come_claimed_mode || 'hide',
|
||||
dependency_mode: this.config.dependency_mode || 'hide',
|
||||
elapsed_time_mode: this.config.elapsed_time_mode || 'dim',
|
||||
show_countdown: this.config.show_countdown !== false,
|
||||
@@ -4953,6 +5078,7 @@ class TaskMateChildCardEditor extends LitElement {
|
||||
show_countdown: true, show_description: false, debug: false,
|
||||
time_category: 'morning', due_days_mode: 'hide',
|
||||
recurrence_done_mode: 'dim', elapsed_time_mode: 'dim',
|
||||
first_come_claimed_mode: 'hide',
|
||||
};
|
||||
if (value === '' || value === null || value === undefined) {
|
||||
delete newConfig[key];
|
||||
|
||||
Reference in New Issue
Block a user