209 files

This commit is contained in:
Home Assistant Version Control
2026-10-07 13:11:05 +00:00
parent 4fef64cc2c
commit 983811cb93
209 changed files with 714 additions and 26066 deletions
@@ -65,6 +65,8 @@
"interval": "Interval",
"warning": "Upozornění",
"last_performed": "Naposledy provedeno",
"last_done_days_ago": "hotovo před {n} d",
"last_done_today": "hotovo dnes",
"next_due": "Další termín",
"days_until_due": "Dnů do termínu",
"avg_duration": "Prům. trvání",
@@ -67,6 +67,8 @@
"interval": "Interval",
"warning": "Advarsel",
"last_performed": "Senest udført",
"last_done_days_ago": "udført for {n} d siden",
"last_done_today": "udført i dag",
"next_due": "Næste forfald",
"days_until_due": "Dage til forfald",
"avg_duration": "Gns. varighed",
@@ -67,6 +67,8 @@
"interval": "Intervall",
"warning": "Vorwarnung",
"last_performed": "Zuletzt durchgeführt",
"last_done_days_ago": "vor {n} T erledigt",
"last_done_today": "heute erledigt",
"next_due": "Nächste Fälligkeit",
"days_until_due": "Tage bis fällig",
"avg_duration": "Ø Dauer",
@@ -67,6 +67,8 @@
"interval": "Interval",
"warning": "Warning",
"last_performed": "Last performed",
"last_done_days_ago": "done {n} d ago",
"last_done_today": "done today",
"next_due": "Next due",
"days_until_due": "Days until due",
"avg_duration": "Avg duration",
@@ -65,6 +65,8 @@
"interval": "Intervalo",
"warning": "Aviso",
"last_performed": "Última ejecución",
"last_done_days_ago": "hecho hace {n} d",
"last_done_today": "hecho hoy",
"next_due": "Próximo vencimiento",
"days_until_due": "Días hasta vencimiento",
"avg_duration": "Ø Duración",
@@ -67,6 +67,8 @@
"interval": "Väli",
"warning": "Varoitus",
"last_performed": "Viimeksi suoritettu",
"last_done_days_ago": "tehty {n} pv sitten",
"last_done_today": "tehty tänään",
"next_due": "Seuraava eräpäivä",
"days_until_due": "Päiviä eräpäivään",
"avg_duration": "Keskim. kesto",
@@ -65,6 +65,8 @@
"interval": "Intervalle",
"warning": "Avertissement",
"last_performed": "Dernière exécution",
"last_done_days_ago": "fait il y a {n} j",
"last_done_today": "fait aujourd’hui",
"next_due": "Prochaine échéance",
"days_until_due": "Jours restants",
"avg_duration": "Ø Durée",
@@ -67,6 +67,8 @@
"interval": "अंतराल",
"warning": "चेतावनी",
"last_performed": "अंतिम बार किया गया",
"last_done_days_ago": "{n} दिन पहले किया",
"last_done_today": "आज किया",
"next_due": "अगली देय",
"days_until_due": "देय तक दिन",
"avg_duration": "औसत अवधि",
@@ -67,6 +67,8 @@
"interval": "Intervallum",
"warning": "Figyelmeztetés",
"last_performed": "Utoljára elvégezve",
"last_done_days_ago": "{n} napja elvégezve",
"last_done_today": "ma elvégezve",
"next_due": "Következő esedékesség",
"days_until_due": "Napok az esedékességig",
"avg_duration": "Átl. időtartam",
@@ -65,6 +65,8 @@
"interval": "Intervallo",
"warning": "Avviso",
"last_performed": "Ultima esecuzione",
"last_done_days_ago": "fatto {n} g fa",
"last_done_today": "fatto oggi",
"next_due": "Prossima scadenza",
"days_until_due": "Giorni alla scadenza",
"avg_duration": "Ø Durata",
@@ -67,6 +67,8 @@
"interval": "間隔",
"warning": "警告",
"last_performed": "最終実施",
"last_done_days_ago": "{n}日前に実施",
"last_done_today": "今日実施",
"next_due": "次回期限",
"days_until_due": "期限までの日数",
"avg_duration": "平均所要時間",
@@ -67,6 +67,8 @@
"interval": "주기",
"warning": "경고",
"last_performed": "마지막 수행일",
"last_done_days_ago": "{n}일 전 완료",
"last_done_today": "오늘 완료",
"next_due": "다음 예정일",
"days_until_due": "남은 일수",
"avg_duration": "평균 소요 시간",
@@ -67,6 +67,8 @@
"interval": "Intervall",
"warning": "Advarsel",
"last_performed": "Sist utført",
"last_done_days_ago": "utført for {n} d siden",
"last_done_today": "utført i dag",
"next_due": "Neste forfall",
"days_until_due": "Dager til forfall",
"avg_duration": "Gj.snittlig varighet",
@@ -65,6 +65,8 @@
"interval": "Interval",
"warning": "Waarschuwing",
"last_performed": "Laatst uitgevoerd",
"last_done_days_ago": "{n} d geleden gedaan",
"last_done_today": "vandaag gedaan",
"next_due": "Volgende keer",
"days_until_due": "Dagen tot vervaldatum",
"avg_duration": "Ø Duur",
@@ -65,6 +65,8 @@
"interval": "Interwał",
"warning": "Ostrzeżenie",
"last_performed": "Ostatnio wykonane",
"last_done_days_ago": "wykonano {n} d temu",
"last_done_today": "wykonano dziś",
"next_due": "Następny termin",
"days_until_due": "Dni do terminu",
"avg_duration": "Śr. czas trwania",
@@ -67,6 +67,8 @@
"interval": "Intervalo",
"warning": "Aviso",
"last_performed": "Última execução",
"last_done_days_ago": "feito há {n} d",
"last_done_today": "feito hoje",
"next_due": "Próximo vencimento",
"days_until_due": "Dias até o vencimento",
"avg_duration": "Duração média",
@@ -65,6 +65,8 @@
"interval": "Intervalo",
"warning": "Aviso",
"last_performed": "Última execução",
"last_done_days_ago": "feito há {n} d",
"last_done_today": "feito hoje",
"next_due": "Próximo vencimento",
"days_until_due": "Dias até vencimento",
"avg_duration": "Ø Duração",
@@ -65,6 +65,8 @@
"interval": "Интервал",
"warning": "Предупреждение",
"last_performed": "Последнее выполнение",
"last_done_days_ago": "выполнено {n} дн. назад",
"last_done_today": "выполнено сегодня",
"next_due": "Следующий срок",
"days_until_due": "Дней до срока",
"avg_duration": "Ср. длительность",
@@ -65,6 +65,8 @@
"interval": "Intervall",
"warning": "Varning",
"last_performed": "Senast utförd",
"last_done_days_ago": "klar för {n} d sedan",
"last_done_today": "klar i dag",
"next_due": "Nästa förfallodatum",
"days_until_due": "Dagar till förfallodatum",
"avg_duration": "Snittlig varaktighet",
@@ -67,6 +67,8 @@
"interval": "Aralık",
"warning": "Uyarı",
"last_performed": "Son yapılma",
"last_done_days_ago": "{n} g önce yapıldı",
"last_done_today": "bugün yapıldı",
"next_due": "Sonraki vade",
"days_until_due": "Vadeye kalan gün",
"avg_duration": "Ort. süre",
@@ -65,6 +65,8 @@
"interval": "Інтервал",
"warning": "Попередження",
"last_performed": "Останнє виконання",
"last_done_days_ago": "виконано {n} д тому",
"last_done_today": "виконано сьогодні",
"next_due": "Наступний термін",
"days_until_due": "Днів до терміну",
"avg_duration": "Сер. тривалість",
@@ -67,6 +67,8 @@
"interval": "间隔",
"warning": "预警",
"last_performed": "上次执行",
"last_done_days_ago": "{n} 天前完成",
"last_done_today": "今天完成",
"next_due": "下次到期",
"days_until_due": "距离到期天数",
"avg_duration": "平均耗时",
@@ -4,7 +4,7 @@ import { LitElement, html, css, nothing } from "lit";
import { applySubscriptionEvent, type SubscriptionEvent } from "./helpers/subscription-merge";
import { hydrateObjects } from "./helpers/hydrate-objects";
import { property, state } from "lit/decorators.js";
import { syncLocaleFromHass, sharedStyles, STATUS_COLORS, t, ensureLocale, isLocaleLoaded, setProfilePrefs, formatDueDays, langOf, currencySymbolOf, syncCurrencyDecimals} from "./styles";
import { syncLocaleFromHass, sharedStyles, STATUS_COLORS, t, ensureLocale, isLocaleLoaded, setProfilePrefs, formatDueDays, formatLastDone, formatDate, langOf, currencySymbolOf, syncCurrencyDecimals} from "./styles";
import { openSignedDocument } from "./helpers/document-url";
import { isPhotoDocument } from "./helpers/document-categories";
import { isSafeHttpUrl } from "./helpers/url";
@@ -207,6 +207,15 @@ export class MaintenanceSupporterCard extends LitElement {
this._firstLoadDone = true;
}
/** A task without a due date (manual, sensor-only, paused): when it was
* last done, "done 23 d ago" (D#203) — a dash only when never done. */
private _lastDone(task: MaintenanceTask, L: string) {
const done = formatLastDone(task.last_performed, L);
return done
? html`<span class="last-done" title="${t("last_performed", L)}: ${formatDate(task.last_performed, L)}">${done}</span>`
: "—";
}
/** Display name of the task's responsible user, or "" when the badge must
* stay hidden (feature off, nobody assigned, or the name not resolved).
* With a rotation this is whoever is up next — the pointer the engine
@@ -603,7 +612,7 @@ export class MaintenanceSupporterCard extends LitElement {
: formatDueDays(task.days_until_due, L)
: task.trigger_active
? "⚡"
: "—"}
: this._lastDone(task, L)}
</div>
${showActions && useButtons
? html`
@@ -116,7 +116,7 @@ import type { MaintenanceSavedViewsDialog } from "./components/saved-views-dialo
import { type SparklineContext } from "./renderers/sparkline";
import { buildCalendarBuckets, type CalendarEvent } from "./helpers/calendar-bucket";
import { haToday } from "./helpers/ha-time";
import { renderTriggerProgress, renderMiniSparkline, computeTrend } from "./renderers/progress";
import { renderTriggerProgress, renderMiniSparkline, computeTrend, renderDueText } from "./renderers/progress";
import { type HistoryContext } from "./renderers/history";
import { renderUserBadge, type TaskDetailContext } from "./renderers/task-detail";
import { renderPersonChip, type PersonDisplay } from "./helpers/person";
@@ -1248,6 +1248,7 @@ export class MaintenanceSupporterPanel extends LitElement {
status: task.status,
days_until_due: task.days_until_due ?? null,
next_due: task.next_due ?? null,
last_performed: task.last_performed ?? null,
next_event_titles: task.next_event_titles ?? [],
trigger_active: task.trigger_active,
trigger_current_value: task.trigger_current_value ?? null,
@@ -4615,7 +4616,7 @@ export class MaintenanceSupporterPanel extends LitElement {
</span>
<span class="cell type">${t(row.type, L)}</span>
<span class="due-cell" @click=${() => this._showTask(row.entry_id, row.task_id)}>
<span class="due-text">${formatDueDays(row.days_until_due, L)}</span>
${renderDueText(row.days_until_due, row.last_performed, L)}
${hasDaysBar
? html`<div class="days-bar"><div class="days-bar-fill${daysOverflow ? " overflow" : ""}" style="width:${pct}%;background:${barColor}"></div></div>`
: nothing}
@@ -4845,7 +4846,7 @@ export class MaintenanceSupporterPanel extends LitElement {
<span class="task-sub${task.responsible_user_id ? '' : ' task-sub-empty'}">${renderUserBadge(task, (id) => this._userService?.getUserName(id) ?? null, (id) => this._userService?.getPerson(id) ?? null)}</span>
<span class="cell type">${t(task.type, L)}</span>
<span class="due-cell" @click=${() => this._showTask(obj.entry_id, task.id)}>
<span class="due-text">${formatDueDays(task.days_until_due, L)}</span>
${renderDueText(task.days_until_due, task.last_performed, L)}
${task.trigger_config
? renderTriggerProgress(task, { trend: computeTrend(task, this._miniStatsData), lang: L })
: nothing}
@@ -12,7 +12,7 @@
*/
import { html, nothing } from "lit";
import { t, formatDate, formatDueDays, formatNumber } from "../styles";
import { t, formatDate, formatDueDays, formatLastDone, formatNumber } from "../styles";
import { px } from "./chart-utils";
import { daysProgress } from "../helpers/interval";
import { stampMs } from "../helpers/ha-time";
@@ -296,6 +296,24 @@ export function renderMiniSparkline(
`;
}
/** The due column's text: days until due, or, for a task without a due date
* (manual, sensor-only, paused), when it was last done (D#203) — muted, so
* it never reads as a status. A bare dash only for a task never done. */
export function renderDueText(
days: number | null | undefined,
lastPerformed: string | null | undefined,
lang: string,
cls = "due-text",
) {
if (days == null) {
const done = formatLastDone(lastPerformed, lang);
if (done) {
return html`<span class="${cls} last-done" title="${t("last_performed", lang)}: ${formatDate(lastPerformed, lang)}">${done}</span>`;
}
}
return html`<span class="${cls}">${formatDueDays(days, lang)}</span>`;
}
/** A detailed days-progress bar (last performed → next due) for the detail view. */
export function renderDaysProgress(task: MaintenanceTask, lang: string) {
const L = lang;
@@ -33,7 +33,7 @@ export { STATUS_COLORS, STATUS_ICONS } from "./status-constants";
// `from "./styles"` import keeps working.
import { ensureLocale, isLocaleLoaded, normLang, seedEnglish, t, type Translations } from "./helpers/locale-core";
import { primeBackendErrors } from "./helpers/backend-errors";
import { displayTimeZone, haTimeZone, minuteIn, stampMs, ymdIn } from "./helpers/ha-time";
import { daysBetweenIso, displayTimeZone, haTimeZone, haToday, minuteIn, stampDate, stampMs, ymdIn } from "./helpers/ha-time";
export { ensureLocale, isLocaleLoaded, seedEnglish, setLocale, t } from "./helpers/locale-core";
seedEnglish(EN as Translations);
@@ -316,6 +316,18 @@ export function formatDueDays(days: number | null | undefined, lang?: string): s
return `${days} ${days === 1 ? t("day", l) : t("days", l)}`;
}
/** "done 23 d ago" / "done today" on HA's calendar — what the due column
* shows for a task without a due date (manual, sensor-only, paused) instead
* of a bare dash (D#203). null when the task was never done. */
export function formatLastDone(lastPerformed: string | null | undefined, lang?: string): string | null {
const day = stampDate(lastPerformed);
if (!day) return null;
const days = daysBetweenIso(day, haToday());
if (!Number.isFinite(days)) return null;
if (days <= 0) return t("last_done_today", lang || "en");
return t("last_done_days_ago", lang || "en").replace("{n}", String(days));
}
/** Localized interval label, e.g. "3 Months" / "7 Days" (issue #59 — was
* hard-coded to days). Uses the unit_* keys from the schedule dialog. */
export function formatInterval(
@@ -1045,6 +1057,9 @@ export const sharedStyles = css`
}
.due-text { font-size: 13px; }
/* D#203: a task without a due date shows when it was last done — muted,
so it never reads as a status. */
.due-text.last-done { color: var(--secondary-text-color); }
.days-bar {
width: 100%;
@@ -599,6 +599,8 @@ export interface TaskRow {
status: string;
days_until_due: number | null;
next_due: string | null;
/** D#203: shown as "done N d ago" when the task has no due date. */
last_performed?: string | null;
trigger_active: boolean;
trigger_current_value: number | null;
trigger_current_delta: number | null;