Skip to content

Commit

Permalink
Option to sort todo lists
Browse files Browse the repository at this point in the history
  • Loading branch information
karwosts committed Jan 4, 2025
1 parent 87884ac commit c85a73e
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 33 deletions.
76 changes: 60 additions & 16 deletions src/panels/lovelace/cards/hui-todo-list-card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import memoizeOne from "memoize-one";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { supportsFeature } from "../../../common/entity/supports-feature";
import { stopPropagation } from "../../../common/dom/stop_propagation";
import { caseInsensitiveStringCompare } from "../../../common/string/compare";
import "../../../components/ha-card";
import "../../../components/ha-check-list-item";
import "../../../components/ha-checkbox";
Expand Down Expand Up @@ -51,6 +52,10 @@ import { createEntityNotFoundWarning } from "../components/hui-warning";
import type { LovelaceCard, LovelaceCardEditor } from "../types";
import type { TodoListCardConfig } from "./types";

export const SORT_ALPHA = "alpha";
export const SORT_DUEDATE = "duedate";
export const SORT_NONE = "none";

@customElement("hui-todo-list-card")
export class HuiTodoListCard extends LitElement implements LovelaceCard {
public static async getConfigElement(): Promise<LovelaceCardEditor> {
Expand Down Expand Up @@ -123,16 +128,47 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard {
return undefined;
}

private _getCheckedItems = memoizeOne((items?: TodoItem[]): TodoItem[] =>
items
? items.filter((item) => item.status === TodoItemStatus.Completed)
: []
private _sortItems(items: TodoItem[], sort?: string | undefined) {
if (sort === SORT_ALPHA) {
return items.sort((a, b) =>
caseInsensitiveStringCompare(
a.summary,
b.summary,
this.hass?.locale.language
)
);
}
if (sort === SORT_DUEDATE) {
return items.sort((a, b) => {
const aDue = this._getDueDate(a) ?? Infinity;
const bDue = this._getDueDate(b) ?? Infinity;
if (aDue === bDue) {
return 0;
}
return aDue < bDue ? -1 : 1;
});
}
return items;
}

private _getCheckedItems = memoizeOne(
(items?: TodoItem[], sort?: string | undefined): TodoItem[] =>
items
? this._sortItems(
items.filter((item) => item.status === TodoItemStatus.Completed),
sort
)
: []
);

private _getUncheckedItems = memoizeOne((items?: TodoItem[]): TodoItem[] =>
items
? items.filter((item) => item.status === TodoItemStatus.NeedsAction)
: []
private _getUncheckedItems = memoizeOne(
(items?: TodoItem[], sort?: string | undefined): TodoItem[] =>
items
? this._sortItems(
items.filter((item) => item.status === TodoItemStatus.NeedsAction),
sort
)
: []
);

public willUpdate(
Expand Down Expand Up @@ -185,8 +221,11 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard {

const unavailable = isUnavailableState(stateObj.state);

const checkedItems = this._getCheckedItems(this._items);
const uncheckedItems = this._getUncheckedItems(this._items);
const checkedItems = this._getCheckedItems(this._items, this._config.sort);
const uncheckedItems = this._getUncheckedItems(
this._items,
this._config.sort
);

return html`
<ha-card
Expand Down Expand Up @@ -236,7 +275,8 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard {
"ui.panel.lovelace.cards.todo-list.unchecked_items"
)}
</h2>
${this._todoListSupportsFeature(
${(!this._config.sort || this._config.sort === SORT_NONE) &&
this._todoListSupportsFeature(
TodoListEntityFeature.MOVE_TODO_ITEM
)
? html`<ha-button-menu @closed=${stopPropagation}>
Expand Down Expand Up @@ -317,6 +357,14 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard {
`;
}

private _getDueDate(item: TodoItem): Date | undefined {
return item.due
? item.due.includes("T")
? new Date(item.due)
: endOfDay(new Date(`${item.due}T00:00:00`))
: undefined;
}

private _renderItems(items: TodoItem[], unavailable = false) {
return html`
${repeat(
Expand All @@ -332,11 +380,7 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard {
);
const showReorder =
item.status !== TodoItemStatus.Completed && this._reordering;
const due = item.due
? item.due.includes("T")
? new Date(item.due)
: endOfDay(new Date(`${item.due}T00:00:00`))
: undefined;
const due = this._getDueDate(item);
const today =
due && !item.due!.includes("T") && isSameDay(new Date(), due);
return html`
Expand Down
1 change: 1 addition & 0 deletions src/panels/lovelace/cards/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,7 @@ export interface TodoListCardConfig extends LovelaceCardConfig {
theme?: string;
entity?: string;
hide_completed?: boolean;
sort?: string;
}

export interface StackCardConfig extends LovelaceCardConfig {
Expand Down
61 changes: 45 additions & 16 deletions src/panels/lovelace/editor/config-elements/hui-todo-list-editor.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
import type { CSSResultGroup } from "lit";
import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { assert, assign, boolean, object, optional, string } from "superstruct";
import { isComponentLoaded } from "../../../../common/config/is_component_loaded";
import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-alert";
import "../../../../components/ha-form/ha-form";
import type { HomeAssistant } from "../../../../types";
import type { LocalizeFunc } from "../../../../common/translations/localize";
import type { TodoListCardConfig } from "../../cards/types";
import type { LovelaceCardEditor } from "../../types";
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
import type { SchemaUnion } from "../../../../components/ha-form/types";
import { configElementStyle } from "./config-elements-style";
import {
SORT_ALPHA,
SORT_DUEDATE,
SORT_NONE,
} from "../../cards/hui-todo-list-card";

const cardConfigStruct = assign(
baseLovelaceCardConfig,
Expand All @@ -20,21 +27,10 @@ const cardConfigStruct = assign(
theme: optional(string()),
entity: optional(string()),
hide_completed: optional(boolean()),
sort: optional(string()),
})
);

const SCHEMA = [
{ name: "title", selector: { text: {} } },
{
name: "entity",
selector: {
entity: { domain: "todo" },
},
},
{ name: "theme", selector: { theme: {} } },
{ name: "hide_completed", selector: { boolean: {} } },
] as const;

@customElement("hui-todo-list-card-editor")
export class HuiTodoListEditor
extends LitElement
Expand All @@ -44,6 +40,36 @@ export class HuiTodoListEditor

@state() private _config?: TodoListCardConfig;

private _schema = memoizeOne(
(localize: LocalizeFunc) =>
[
{ name: "title", selector: { text: {} } },
{
name: "entity",
selector: {
entity: { domain: "todo" },
},
},
{ name: "theme", selector: { theme: {} } },
{ name: "hide_completed", selector: { boolean: {} } },
{
name: "sort",
selector: {
select: {
options: [SORT_NONE, SORT_ALPHA, SORT_DUEDATE].map((sort) => ({
value: sort,
label: localize(
`ui.panel.lovelace.editor.card.todo-list.sort_modes.${sort}`
),
})),
},
},
},
] as const
);

private _data = memoizeOne((config) => ({ sort: "none", ...config }));

public setConfig(config: TodoListCardConfig): void {
assert(config, cardConfigStruct);
this._config = config;
Expand All @@ -68,8 +94,8 @@ export class HuiTodoListEditor
}
<ha-form
.hass=${this.hass}
.data=${this._config}
.schema=${SCHEMA}
.data=${this._data(this._config)}
.schema=${this._schema(this.hass.localize)}
.computeLabel=${this._computeLabelCallback}
@value-changed=${this._valueChanged}
></ha-form>
Expand All @@ -82,7 +108,9 @@ export class HuiTodoListEditor
fireEvent(this, "config-changed", { config });
}

private _computeLabelCallback = (schema: SchemaUnion<typeof SCHEMA>) => {
private _computeLabelCallback = (
schema: SchemaUnion<ReturnType<typeof this._schema>>
) => {
switch (schema.name) {
case "theme":
return `${this.hass!.localize(
Expand All @@ -91,8 +119,9 @@ export class HuiTodoListEditor
"ui.panel.lovelace.editor.card.config.optional"
)})`;
case "hide_completed":
case "sort":
return this.hass!.localize(
"ui.panel.lovelace.editor.card.todo-list.hide_completed"
`ui.panel.lovelace.editor.card.todo-list.${schema.name}`
);
default:
return this.hass!.localize(
Expand Down
8 changes: 7 additions & 1 deletion src/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -6563,7 +6563,13 @@
"name": "To-do list",
"description": "The To-do list card allows you to add, edit, check-off, and remove items from your to-do list.",
"integration_not_loaded": "This card requires the `todo` integration to be set up.",
"hide_completed": "Hide completed items"
"hide_completed": "Hide completed items",
"sort": "Sort Items",
"sort_modes": {
"none": "None",
"alpha": "Alphabetical",
"duedate": "Due Date"
}
},
"thermostat": {
"name": "Thermostat",
Expand Down

0 comments on commit c85a73e

Please sign in to comment.