Search To-do Lists

After an extensive search for a TODO list search code, which was unsuccessful, help was sought from Gemini, which provided functional code.

Since programming experience is limited, optimization of the code would be appreciated. The final version should be saved in www/todo_search/todo-search-card.js

Installation steps:

  1. Go to Settings > Dashboards.
  2. Click the three dots in the upper right corner and select Resources.
  3. Click Add Resource and enter the following path: /local/todo_search/todo-search-card.js
  4. Ensure the resource type is set to JavaScript Module.
  5. Restart Home Assistant.
  6. To use it, add a Manual Card to your dashboard with the following configuration:
    type: custom:todo-search-card

todo-search-card.js:

class TodoSearchCard extends HTMLElement {
setConfig(config) {
this.config = config;
}

set hass(hass) {
const oldHass = this._hass;
this._hass = hass;

if (!this.content) {
  this._createCard();
}

if (!oldHass || oldHass.states !== hass.states) {
  this._loadAllTodos();
}

}

_createCard() {
this.attachShadow({ mode: “open” });
this.shadowRoot.innerHTML = <style> ha-card { padding: 16px; direction: ltr; } .search-container { position: relative; margin-bottom: 16px; } input { width: 100%; padding: 12px; padding-right: 40px; box-sizing: border-box; border-radius: 4px; border: 1px solid var(--divider-color); background: var(--card-background-color); color: var(--primary-text-color); } .clear-btn { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); cursor: pointer; color: var(--secondary-text-color); font-weight: bold; border: none; background: none; font-size: 18px; display: none; } .list-section { margin-bottom: 16px; border-bottom: 1px solid var(--divider-color); } .list-title { font-weight: bold; color: var(--accent-color); margin-bottom: 8px; } .item { display: flex; justify-content: space-between; align-items: center; padding: 8px 0; border-top: 1px solid var(--secondary-background-color); } .action-btn { background: var(--error-color); color: white; border: none; border-radius: 4px; padding: 4px 8px; cursor: pointer; } .no-results { color: var(--secondary-text-color); text-align: center; margin-top: 10px; } </style> <ha-card> <div class="search-container"> <input type="text" id="searchInput" placeholder="Search lists..."> <button class="clear-btn" id="clearBtn">×</button> </div> <div id="results"></div> </ha-card> ;

const input = this.shadowRoot.querySelector("#searchInput");
const clearBtn = this.shadowRoot.querySelector("#clearBtn");
this.content = this.shadowRoot.querySelector("#results");

input.addEventListener("input", (e) => {
  this._searchTerm = e.target.value.toLowerCase();
  clearBtn.style.display = this._searchTerm ? "block" : "none";
  this._renderResults();
});

clearBtn.addEventListener("click", () => {
  input.value = "";
  this._searchTerm = "";
  clearBtn.style.display = "none";
  this._renderResults();
});

}

async _loadAllTodos() {
const todoEntities = Object.keys(this._hass.states).filter(eid => eid.startsWith(‘todo.’));
this.allTodos = {};

for (const entity of todoEntities) {
  try {
    const result = await this._hass.callWS({
      type: "todo/item/list",
      entity_id: entity,
    });
    this.allTodos[entity] = result.items;
  } catch (e) {
    console.error("Error loading todos for", entity, e);
  }
}
this._renderResults();

}

_renderResults() {
if (!this.allTodos || !this.content) return;
const q = this._searchTerm || “”;
this.content.innerHTML = “”;

if (q.length === 0) return;

let foundAny = false;

for (const [entityId, items] of Object.entries(this.allTodos)) {
  const filtered = items.filter(i => i.summary.toLowerCase().includes(q) && i.status === "needs_action");
  if (filtered.length === 0) continue;

  foundAny = true;
  const section = document.createElement("div");
  section.className = "list-section";
  
  const friendlyName = this._hass.states[entityId].attributes.friendly_name || entityId;
  section.innerHTML = `<div class="list-title">${friendlyName}</div>`;

  filtered.forEach(item => {
    const row = document.createElement("div");
    row.className = "item";
    row.innerHTML = `<span>${item.summary}</span>`;
    
    const btn = document.createElement("button");
    btn.className = "action-btn";
    btn.textContent = "Done";
    btn.onclick = async () => {
      await this._hass.callService("todo", "update_item", {
        entity_id: entityId,
        item: item.uid || item.summary,
        status: "completed"
      });
      this._loadAllTodos();
    };
    
    row.appendChild(btn);
    section.appendChild(row);
  });
  this.content.appendChild(section);
}

if (!foundAny) {
  const noResults = document.createElement("div");
  noResults.className = "no-results";
  noResults.textContent = `No results found for "${q}"`;
  this.content.appendChild(noResults);
}

}

getCardSize() { return 5; }
}

customElements.define(“todo-search-card”, TodoSearchCard);

Which was also unsuccessful. Public LLMs are HORRIBLE at HA scripting without precise coaching in what not to do.

I have no idea what that is it’s a mishmash of Javascript and. It’s trying to build a Javascript module to? Just no…

Let’s start over…

What are you actually trying to do?

I have a lot of todo lists and i want to search all of them for items…
The code is working for me.

If you have the knowledge, I would be more than happy if you would post clean code…