This commit is contained in:
Home Assistant Version Control
2026-07-31 16:29:09 +00:00
parent d125a48b04
commit 8c53907c88
91 changed files with 1232 additions and 387 deletions
@@ -19,6 +19,9 @@ interface BatteryRow {
days_until: number | null;
available?: boolean;
}
interface RosterRow extends BatteryRow {
status: "low" | "soon" | "ok";
}
interface Overview {
available: boolean;
configured: boolean;
@@ -26,6 +29,9 @@ interface Overview {
total: number;
low: BatteryRow[];
soon: BatteryRow[];
// Every tracked battery, whatever its state. Optional so an older backend
// simply renders no roster instead of throwing.
all?: RosterRow[];
needs_now: Record<string, number>;
needs_soon: Record<string, number>;
types: string[];
@@ -133,6 +139,16 @@ export class MaintenanceBatteryFleetSection extends LitElement {
}
}
/** The forecast as a date a person can plan with, not a day count.
* `days_until` comes from last-replaced + typical lifetime, so it is an
* estimate — the tilde in the template says so. Negative values (past the
* typical lifetime but not reported low yet) render as past dates, which
* is honest: the battery is living on borrowed time. */
private _predictedDate(daysUntil: number): string {
const when = new Date(Date.now() + daysUntil * 864e5);
return new Intl.DateTimeFormat(this._lang, { day: "numeric", month: "numeric", year: "numeric" }).format(when);
}
private _shoppingLine(needs: Record<string, number>): string {
return Object.entries(needs)
.map(([type, qty]) => `${qty}× ${type}`)
@@ -220,6 +236,41 @@ export class MaintenanceBatteryFleetSection extends LitElement {
</div>
`
: nothing}
${ov.all?.length
? html`
<details class="bf-roster">
<summary>${t("battery_fleet_all", L)} (${ov.all.length})</summary>
<div class="bf-rows">
${ov.all.map(
(b) => html`
<div class="bf-row">
<span class="bf-dev">${b.device_name}</span>
<span class="bf-status bf-${b.status}">${t("battery_fleet_status_" + b.status, L)}</span>
<span class="bf-type">${b.quantity}× ${b.battery_type}</span>
${b.level != null ? html`<span class="bf-level">${b.level}%</span>` : nothing}
${b.days_until != null
? html`<span
class="bf-predicted"
title=${t("battery_fleet_predicted_on", L).replace("{date}", this._predictedDate(b.days_until))}
>~${this._predictedDate(b.days_until)}</span
>`
: nothing}
<button
class="bf-mark bf-exclude"
title=${t("battery_fleet_exclude", L)}
.disabled=${this._marking}
@click=${() => this._setExcluded(b.entity_id, true)}
>
<ha-icon icon="mdi:eye-off-outline"></ha-icon>
</button>
</div>
`,
)}
</div>
<div class="bf-roster-hint">${t("battery_fleet_all_hint", L)}</div>
</details>
`
: nothing}
${ov.excluded?.length
? html`
<div class="bf-excluded">
@@ -363,6 +414,40 @@ export class MaintenanceBatteryFleetSection extends LitElement {
font-size: 12px;
color: var(--secondary-text-color);
}
/* The roster is a lookup list, not the headline — collapsed by default so
the section still opens on what actually needs doing. */
.bf-roster > summary {
cursor: pointer;
font-size: 13px;
color: var(--secondary-text-color);
padding: 2px 0;
}
.bf-roster-hint {
font-size: 12px;
color: var(--secondary-text-color);
padding-top: 6px;
}
.bf-status {
font-size: 11px;
padding: 1px 7px;
border-radius: 9px;
white-space: nowrap;
background: var(--secondary-background-color, rgba(127, 127, 127, 0.15));
color: var(--secondary-text-color);
}
.bf-status.bf-low {
background: var(--error-color, #f44336);
color: #fff;
}
.bf-status.bf-soon {
background: var(--warning-color, #ff9800);
color: #fff;
}
.bf-predicted {
font-size: 12px;
color: var(--secondary-text-color);
white-space: nowrap;
}
.bf-total {
font-size: 12px;
color: var(--secondary-text-color);
@@ -814,6 +814,12 @@
"battery_fleet_exclude": "Vyloučit z flotily",
"battery_fleet_excluded": "Vyloučené",
"battery_fleet_include": "Znovu sledovat",
"battery_fleet_all": "Všechny sledované baterie",
"battery_fleet_all_hint": "Vylučte zařízení už dřív, než vůbec ohlásí nízkou úroveň — vysavač, který se sám nabíjí, nebo telefon, který varuje sám.",
"battery_fleet_status_low": "Nízká",
"battery_fleet_status_soon": "Brzy",
"battery_fleet_status_ok": "V pořádku",
"battery_fleet_predicted_on": "Očekáváno kolem {date}",
"battery_fleet_total": "Sledováno baterií: {n}",
"battery_fleet_setup_button": "Flotila baterií",
"battery_fleet_setup_done": "Flotila baterií nastavena — jeden úkol sleduje všechny.",
@@ -814,6 +814,12 @@
"battery_fleet_exclude": "Udeluk fra flåden",
"battery_fleet_excluded": "Udelukket",
"battery_fleet_include": "Følg igen",
"battery_fleet_all": "Alle overvågede batterier",
"battery_fleet_all_hint": "Udeluk en enhed her, før den overhovedet melder lavt niveau — en støvsuger, der lader sig selv, eller en telefon, der advarer af sig selv.",
"battery_fleet_status_low": "Lav",
"battery_fleet_status_soon": "Snart",
"battery_fleet_status_ok": "I orden",
"battery_fleet_predicted_on": "Forventes omkring {date}",
"battery_fleet_total": "{n} batterier overvåges",
"battery_fleet_setup_button": "Batteriflåde",
"battery_fleet_setup_done": "Batteriflåde opsat — én opgave følger alle batterier.",
@@ -814,6 +814,12 @@
"battery_fleet_exclude": "Aus der Flotte ausschließen",
"battery_fleet_excluded": "Ausgeschlossen",
"battery_fleet_include": "Wieder aufnehmen",
"battery_fleet_all": "Alle überwachten Batterien",
"battery_fleet_all_hint": "Ein Gerät hier ausschließen, bevor es überhaupt schwach meldet — etwa ein Sauger, der sich selbst lädt, oder ein Telefon, das von sich aus warnt.",
"battery_fleet_status_low": "Schwach",
"battery_fleet_status_soon": "Bald",
"battery_fleet_status_ok": "In Ordnung",
"battery_fleet_predicted_on": "Voraussichtlich um {date}",
"battery_fleet_total": "{n} Batterien überwacht",
"battery_fleet_setup_button": "Batterie-Flotte",
"battery_fleet_setup_done": "Batterie-Flotte eingerichtet — ein Task überwacht alle Batterien.",
@@ -814,6 +814,12 @@
"battery_fleet_exclude": "Exclude from the fleet",
"battery_fleet_excluded": "Excluded",
"battery_fleet_include": "Track again",
"battery_fleet_all": "All tracked batteries",
"battery_fleet_all_hint": "Exclude a device here to drop it from the fleet before it ever reports low — a vacuum that recharges itself, or a phone that warns you on its own.",
"battery_fleet_status_low": "Low",
"battery_fleet_status_soon": "Soon",
"battery_fleet_status_ok": "Healthy",
"battery_fleet_predicted_on": "Expected around {date}",
"battery_fleet_total": "{n} batteries tracked",
"battery_fleet_setup_button": "Battery fleet",
"battery_fleet_setup_done": "Battery fleet set up — one task tracks all your batteries.",
@@ -814,6 +814,12 @@
"battery_fleet_exclude": "Excluir de la flota",
"battery_fleet_excluded": "Excluidas",
"battery_fleet_include": "Seguir de nuevo",
"battery_fleet_all": "Todas las pilas monitorizadas",
"battery_fleet_all_hint": "Excluye aquí un dispositivo antes de que llegue a avisar de carga baja — un aspirador que se recarga solo o un teléfono que ya avisa por su cuenta.",
"battery_fleet_status_low": "Baja",
"battery_fleet_status_soon": "Pronto",
"battery_fleet_status_ok": "Correcta",
"battery_fleet_predicted_on": "Previsto hacia {date}",
"battery_fleet_total": "{n} baterías monitorizadas",
"battery_fleet_setup_button": "Flota de baterías",
"battery_fleet_setup_done": "Flota de baterías configurada — una tarea controla todas.",
@@ -814,6 +814,12 @@
"battery_fleet_exclude": "Sulje pois kannasta",
"battery_fleet_excluded": "Pois suljetut",
"battery_fleet_include": "Seuraa uudelleen",
"battery_fleet_all": "Kaikki seuratut paristot",
"battery_fleet_all_hint": "Sulje laite pois jo ennen kuin se ilmoittaa alhaisesta varauksesta — imuri, joka lataa itsensä, tai puhelin, joka varoittaa itse.",
"battery_fleet_status_low": "Vähissä",
"battery_fleet_status_soon": "Pian",
"battery_fleet_status_ok": "Kunnossa",
"battery_fleet_predicted_on": "Odotettavissa noin {date}",
"battery_fleet_total": "{n} paristoa seurannassa",
"battery_fleet_setup_button": "Paristokanta",
"battery_fleet_setup_done": "Paristokanta määritetty — yksi tehtävä seuraa kaikkia.",
@@ -814,6 +814,12 @@
"battery_fleet_exclude": "Exclure du parc",
"battery_fleet_excluded": "Exclues",
"battery_fleet_include": "Suivre à nouveau",
"battery_fleet_all": "Toutes les piles suivies",
"battery_fleet_all_hint": "Excluez un appareil ici avant même qu'il ne signale une charge faible — un aspirateur qui se recharge seul, ou un téléphone qui vous prévient lui-même.",
"battery_fleet_status_low": "Faible",
"battery_fleet_status_soon": "Bientôt",
"battery_fleet_status_ok": "Bon état",
"battery_fleet_predicted_on": "Prévu vers {date}",
"battery_fleet_total": "{n} piles suivies",
"battery_fleet_setup_button": "Parc de piles",
"battery_fleet_setup_done": "Parc de piles configuré — une tâche suit toutes vos piles.",
@@ -814,6 +814,12 @@
"battery_fleet_exclude": "फ्लीट से बाहर करें",
"battery_fleet_excluded": "बाहर किए गए",
"battery_fleet_include": "फिर से ट्रैक करें",
"battery_fleet_all": "सभी ट्रैक की गई बैटरियाँ",
"battery_fleet_all_hint": "किसी डिवाइस को यहाँ कम बैटरी बताने से पहले ही बाहर रखें — जैसे खुद चार्ज होने वाला वैक्यूम, या खुद ही चेतावनी देने वाला फ़ोन।",
"battery_fleet_status_low": "कम",
"battery_fleet_status_soon": "जल्द",
"battery_fleet_status_ok": "ठीक",
"battery_fleet_predicted_on": "{date} के आसपास अपेक्षित",
"battery_fleet_total": "{n} बैटरियाँ ट्रैक की गईं",
"battery_fleet_setup_button": "बैटरी फ्लीट",
"battery_fleet_setup_done": "बैटरी फ्लीट सेट — एक कार्य सभी बैटरियों को ट्रैक करता है।",
@@ -814,6 +814,12 @@
"battery_fleet_exclude": "Kizárás a flottából",
"battery_fleet_excluded": "Kizárva",
"battery_fleet_include": "Követés újra",
"battery_fleet_all": "Minden nyomon követett elem",
"battery_fleet_all_hint": "Zárjon ki egy eszközt már azelőtt, hogy alacsony töltöttséget jelezne — például egy magát töltő porszívót vagy egy telefont, amely magától figyelmeztet.",
"battery_fleet_status_low": "Alacsony",
"battery_fleet_status_soon": "Hamarosan",
"battery_fleet_status_ok": "Rendben",
"battery_fleet_predicted_on": "Várhatóan {date} körül",
"battery_fleet_total": "{n} elem követve",
"battery_fleet_setup_button": "Elemflotta",
"battery_fleet_setup_done": "Elemflotta beállítva — egyetlen feladat követi az összes elemet.",
@@ -814,6 +814,12 @@
"battery_fleet_exclude": "Escludi dal parco",
"battery_fleet_excluded": "Escluse",
"battery_fleet_include": "Traccia di nuovo",
"battery_fleet_all": "Tutte le batterie monitorate",
"battery_fleet_all_hint": "Escludi qui un dispositivo prima ancora che segnali la batteria scarica — un aspirapolvere che si ricarica da solo o un telefono che avvisa da sé.",
"battery_fleet_status_low": "Scarica",
"battery_fleet_status_soon": "A breve",
"battery_fleet_status_ok": "A posto",
"battery_fleet_predicted_on": "Previsto intorno al {date}",
"battery_fleet_total": "{n} batterie monitorate",
"battery_fleet_setup_button": "Parco batterie",
"battery_fleet_setup_done": "Parco batterie configurato — un'attività monitora tutte.",
@@ -814,6 +814,12 @@
"battery_fleet_exclude": "フリートから除外",
"battery_fleet_excluded": "除外済み",
"battery_fleet_include": "再び追跡",
"battery_fleet_all": "追跡中のすべての電池",
"battery_fleet_all_hint": "電池残量が少ないと報告される前に、ここで機器を除外できます — 自動充電するロボット掃除機や、自分で警告するスマートフォンなど。",
"battery_fleet_status_low": "残量低下",
"battery_fleet_status_soon": "まもなく",
"battery_fleet_status_ok": "良好",
"battery_fleet_predicted_on": "{date} 頃の見込み",
"battery_fleet_total": "{n} 個の電池を監視",
"battery_fleet_setup_button": "電池フリート",
"battery_fleet_setup_done": "電池フリートを設定 — 1つのタスクで全電池を管理。",
@@ -814,6 +814,12 @@
"battery_fleet_exclude": "플릿에서 제외",
"battery_fleet_excluded": "제외됨",
"battery_fleet_include": "다시 추적",
"battery_fleet_all": "추적 중인 모든 배터리",
"battery_fleet_all_hint": "배터리 부족을 알리기 전에 여기에서 기기를 제외하세요 — 스스로 충전하는 로봇청소기나 자체적으로 경고하는 휴대폰처럼.",
"battery_fleet_status_low": "부족",
"battery_fleet_status_soon": "곧",
"battery_fleet_status_ok": "정상",
"battery_fleet_predicted_on": "{date}쯤 예상",
"battery_fleet_total": "배터리 {n}개 추적 중",
"battery_fleet_setup_button": "배터리 플릿",
"battery_fleet_setup_done": "배터리 플릿이 설정되었습니다 — 하나의 작업이 모든 배터리를 추적합니다.",
@@ -814,6 +814,12 @@
"battery_fleet_exclude": "Utelukk fra flåten",
"battery_fleet_excluded": "Utelukket",
"battery_fleet_include": "Følg igjen",
"battery_fleet_all": "Alle overvåkede batterier",
"battery_fleet_all_hint": "Utelukk en enhet her før den i det hele tatt melder lavt nivå — en støvsuger som lader seg selv, eller en telefon som varsler av seg selv.",
"battery_fleet_status_low": "Lav",
"battery_fleet_status_soon": "Snart",
"battery_fleet_status_ok": "I orden",
"battery_fleet_predicted_on": "Forventes rundt {date}",
"battery_fleet_total": "{n} batterier spores",
"battery_fleet_setup_button": "Batteriflåte",
"battery_fleet_setup_done": "Batteriflåte satt opp — én oppgave følger alle batteriene.",
@@ -814,6 +814,12 @@
"battery_fleet_exclude": "Uitsluiten van de vloot",
"battery_fleet_excluded": "Uitgesloten",
"battery_fleet_include": "Opnieuw volgen",
"battery_fleet_all": "Alle gevolgde batterijen",
"battery_fleet_all_hint": "Sluit een apparaat hier uit voordat het ooit laag meldt — een stofzuiger die zichzelf oplaadt, of een telefoon die zelf waarschuwt.",
"battery_fleet_status_low": "Laag",
"battery_fleet_status_soon": "Binnenkort",
"battery_fleet_status_ok": "In orde",
"battery_fleet_predicted_on": "Verwacht rond {date}",
"battery_fleet_total": "{n} batterijen gevolgd",
"battery_fleet_setup_button": "Batterijvloot",
"battery_fleet_setup_done": "Batterijvloot ingesteld — één taak volgt al je batterijen.",
@@ -814,6 +814,12 @@
"battery_fleet_exclude": "Wyklucz z floty",
"battery_fleet_excluded": "Wykluczone",
"battery_fleet_include": "Śledź ponownie",
"battery_fleet_all": "Wszystkie śledzone baterie",
"battery_fleet_all_hint": "Wyklucz tutaj urządzenie, zanim w ogóle zgłosi niski poziom — odkurzacz, który sam się ładuje, albo telefon, który ostrzega samodzielnie.",
"battery_fleet_status_low": "Niski",
"battery_fleet_status_soon": "Wkrótce",
"battery_fleet_status_ok": "W porządku",
"battery_fleet_predicted_on": "Przewidywane około {date}",
"battery_fleet_total": "Śledzone baterie: {n}",
"battery_fleet_setup_button": "Flota baterii",
"battery_fleet_setup_done": "Flota baterii skonfigurowana — jedno zadanie śledzi wszystkie.",
@@ -814,6 +814,12 @@
"battery_fleet_exclude": "Remover da frota",
"battery_fleet_excluded": "Removida",
"battery_fleet_include": "Acompanhar novamente",
"battery_fleet_all": "Todas as baterias monitoradas",
"battery_fleet_all_hint": "Exclua aqui um dispositivo antes que ele sequer indique carga baixa — um aspirador que se recarrega sozinho ou um celular que já avisa sozinho.",
"battery_fleet_status_low": "Fraca",
"battery_fleet_status_soon": "Em breve",
"battery_fleet_status_ok": "Em bom estado",
"battery_fleet_predicted_on": "Previsto por volta de {date}",
"battery_fleet_total": "{n} baterias acompanhadas",
"battery_fleet_setup_button": "Frota de baterias",
"battery_fleet_setup_done": "Frota de baterias configurada — uma única tarefa acompanha todas as suas baterias.",
@@ -814,6 +814,12 @@
"battery_fleet_exclude": "Excluir da frota",
"battery_fleet_excluded": "Excluídas",
"battery_fleet_include": "Acompanhar novamente",
"battery_fleet_all": "Todas as pilhas monitorizadas",
"battery_fleet_all_hint": "Exclua aqui um dispositivo antes de ele sequer indicar carga fraca — um aspirador que se recarrega sozinho ou um telemóvel que já avisa por si.",
"battery_fleet_status_low": "Fraca",
"battery_fleet_status_soon": "Em breve",
"battery_fleet_status_ok": "Em bom estado",
"battery_fleet_predicted_on": "Previsto por volta de {date}",
"battery_fleet_total": "{n} baterias monitorizadas",
"battery_fleet_setup_button": "Frota de baterias",
"battery_fleet_setup_done": "Frota de baterias configurada — uma tarefa acompanha todas.",
@@ -814,6 +814,12 @@
"battery_fleet_exclude": "Исключить из парка",
"battery_fleet_excluded": "Исключены",
"battery_fleet_include": "Снова отслеживать",
"battery_fleet_all": "Все отслеживаемые батареи",
"battery_fleet_all_hint": "Исключите устройство здесь ещё до того, как оно сообщит о низком заряде — робот-пылесос, который заряжается сам, или телефон, который предупреждает вас сам.",
"battery_fleet_status_low": "Низкий",
"battery_fleet_status_soon": "Скоро",
"battery_fleet_status_ok": "В норме",
"battery_fleet_predicted_on": "Ожидается примерно {date}",
"battery_fleet_total": "Отслеживается батарей: {n}",
"battery_fleet_setup_button": "Парк батарей",
"battery_fleet_setup_done": "Парк батарей настроен — одна задача следит за всеми.",
@@ -814,6 +814,12 @@
"battery_fleet_exclude": "Uteslut från flottan",
"battery_fleet_excluded": "Uteslutna",
"battery_fleet_include": "Spåra igen",
"battery_fleet_all": "Alla bevakade batterier",
"battery_fleet_all_hint": "Uteslut en enhet här redan innan den rapporterar låg nivå — en dammsugare som laddar sig själv, eller en telefon som varnar på egen hand.",
"battery_fleet_status_low": "Låg",
"battery_fleet_status_soon": "Snart",
"battery_fleet_status_ok": "I ordning",
"battery_fleet_predicted_on": "Väntas omkring {date}",
"battery_fleet_total": "{n} batterier spåras",
"battery_fleet_setup_button": "Batteriflotta",
"battery_fleet_setup_done": "Batteriflotta konfigurerad — en uppgift följer alla batterier.",
@@ -814,6 +814,12 @@
"battery_fleet_exclude": "Filodan hariç tut",
"battery_fleet_excluded": "Hariç tutuldu",
"battery_fleet_include": "Yeniden takip et",
"battery_fleet_all": "İzlenen tüm piller",
"battery_fleet_all_hint": "Bir cihazı, daha düşük şarj bildirmeden burada hariç tutun — kendi kendine şarj olan bir süpürge ya da zaten kendisi uyaran bir telefon gibi.",
"battery_fleet_status_low": "Düşük",
"battery_fleet_status_soon": "Yakında",
"battery_fleet_status_ok": "İyi durumda",
"battery_fleet_predicted_on": "Yaklaşık {date} tarihinde bekleniyor",
"battery_fleet_total": "{n} pil takip ediliyor",
"battery_fleet_setup_button": "Pil filosu",
"battery_fleet_setup_done": "Pil filosu kuruldu — tek bir görev tüm pillerinizi takip ediyor.",
@@ -814,6 +814,12 @@
"battery_fleet_exclude": "Виключити з парку",
"battery_fleet_excluded": "Виключені",
"battery_fleet_include": "Знову відстежувати",
"battery_fleet_all": "Усі відстежувані батареї",
"battery_fleet_all_hint": "Вилучіть пристрій тут ще до того, як він повідомить про низький заряд — робот-пилосос, що заряджається сам, або телефон, який попереджає сам.",
"battery_fleet_status_low": "Низький",
"battery_fleet_status_soon": "Незабаром",
"battery_fleet_status_ok": "У нормі",
"battery_fleet_predicted_on": "Очікується приблизно {date}",
"battery_fleet_total": "Відстежується батарей: {n}",
"battery_fleet_setup_button": "Парк батарей",
"battery_fleet_setup_done": "Парк батарей налаштовано — одне завдання стежить за всіма.",
@@ -814,6 +814,12 @@
"battery_fleet_exclude": "从电池群中排除",
"battery_fleet_excluded": "已排除",
"battery_fleet_include": "重新跟踪",
"battery_fleet_all": "所有跟踪的电池",
"battery_fleet_all_hint": "在设备报告电量不足之前就可以在这里将其排除——例如会自行充电的扫地机器人,或本身就会提醒你的手机。",
"battery_fleet_status_low": "电量低",
"battery_fleet_status_soon": "即将",
"battery_fleet_status_ok": "正常",
"battery_fleet_predicted_on": "预计在 {date} 前后",
"battery_fleet_total": "已跟踪 {n} 个电池",
"battery_fleet_setup_button": "电池群",
"battery_fleet_setup_done": "电池群已设置 — 一个任务跟踪所有电池。",
@@ -20,7 +20,9 @@
* show_user_filter: true # default true
* user_filter: "" # "" | "current_user" | "<uuid>"
* show_object_filter: true # default true; dropdown appears with 2+ objects
* object_filter: "" # "" | "<entry_id>" | "<object name>" (Discussion #83)
* object_filter: "" # "" | "<entry_id>" | "<object name>" — or a LIST of
* # them to restrict the card to several objects; the
* # dropdown then narrows within that set (Discussion #83)
*/
import { LitElement, html, css, nothing } from "lit";
@@ -51,7 +53,7 @@ interface CalendarCardConfig {
user_filter?: string;
/** Discussion #83 — in-card object dropdown + optional preselect. */
show_object_filter?: boolean;
object_filter?: string;
object_filter?: string | string[];
/** v2.2.0 — when set, the card renders past N days from history instead
* of forward N days from next_due. Mutually exclusive with window_days. */
past_days?: PastDays;
@@ -68,6 +70,8 @@ export class MaintenanceCalendarCard extends LitElement {
@state() private _pastDays: PastDays | 0 = 0; // 0 = forward mode
@state() private _userFilter = "";
@state() private _objectFilter = "";
// 2+ configured object_filter values — restricts the card to this set.
private _configuredObjects: string[] = [];
@state() private _unsub: (() => void) | null = null;
private _dataLoaded = false;
@@ -100,6 +104,15 @@ export class MaintenanceCalendarCard extends LitElement {
}
if (typeof config.object_filter === "string") {
this._objectFilter = config.object_filter;
this._configuredObjects = [];
} else if (Array.isArray(config.object_filter)) {
const values = config.object_filter.filter((v): v is string => typeof v === "string" && v !== "");
// One entry behaves exactly like the string form (pre-select, dropdown
// over all objects); two or more RESTRICT the card to that set and the
// dropdown narrows within it — the multi-object parity with the task
// card's filter_objects that #83 asked for.
this._objectFilter = values.length === 1 ? values[0] : "";
this._configuredObjects = values.length > 1 ? values : [];
}
}
@@ -236,16 +249,28 @@ export class MaintenanceCalendarCard extends LitElement {
// Object filter (#83): the config value may be an entry_id or an object
// NAME (friendlier in hand-written YAML) — resolve against the loaded
// objects; an unknown value falls back to "all" instead of a blank card.
const showObjectFilter = this._config.show_object_filter !== false && this._objects.length > 1;
let filterEntryId: string | null = null;
if (this._objectFilter) {
const needle = this._objectFilter.toLowerCase();
const resolve = (value: string): string | null => {
const needle = value.toLowerCase();
const match = this._objects.find(
(o) => o.entry_id === this._objectFilter || o.object.name.toLowerCase() === needle,
(o) => o.entry_id === value || o.object.name.toLowerCase() === needle,
);
filterEntryId = match?.entry_id ?? null;
}
const objects = filterEntryId ? this._objects.filter((o) => o.entry_id === filterEntryId) : this._objects;
return match?.entry_id ?? null;
};
// A configured LIST restricts the card to that set; unknown names resolve
// to nothing and an entirely-unresolved list falls back to all objects
// rather than a blank card (same rule the single value always had).
const configuredIds = new Set(
this._configuredObjects.map(resolve).filter((id): id is string => id !== null),
);
const base = configuredIds.size
? this._objects.filter((o) => configuredIds.has(o.entry_id))
: this._objects;
const showObjectFilter = this._config.show_object_filter !== false && base.length > 1;
const filterEntryId = this._objectFilter ? resolve(this._objectFilter) : null;
const objects =
filterEntryId && base.some((o) => o.entry_id === filterEntryId)
? base.filter((o) => o.entry_id === filterEntryId)
: base;
const today = new Date();
today.setHours(0, 0, 0, 0);
@@ -394,7 +419,7 @@ export class MaintenanceCalendarCard extends LitElement {
this._objectFilter = (e.target as HTMLSelectElement).value;
}}>
<option value="">${t("all_objects", L)}</option>
${[...this._objects]
${[...base]
.sort((a, b) => a.object.name.localeCompare(b.object.name))
.map(
(o) => html`<option value=${o.entry_id} ?selected=${o.entry_id === filterEntryId}>${o.object.name}</option>`,
@@ -569,7 +594,8 @@ class MaintenanceCalendarCardEditor extends LitElement {
/>
</div>
<div class="hint">
Pre-select one object via YAML: object_filter: "&lt;object name&gt;".
Pre-select one object via YAML: object_filter: "&lt;object name&gt;" — or a
list of names to restrict the card to several objects.
</div>
</div>
`;