mirror of
https://github.com/home-assistant/frontend.git
synced 2026-04-02 00:27:49 +01:00
Add due_date_period to todo UI, create period selector (#51263)
* Add due_date_period to todo UI, create period selector * updates * Apply suggestions from code review Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com> * ?? --------- Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
This commit is contained in:
144
src/components/ha-selector/ha-selector-period.ts
Normal file
144
src/components/ha-selector/ha-selector-period.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { PeriodKey, PeriodSelector } from "../../data/selector";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { deepEqual } from "../../common/util/deep-equal";
|
||||
import type { LocalizeFunc } from "../../common/translations/localize";
|
||||
import "../ha-form/ha-form";
|
||||
|
||||
const PERIODS = {
|
||||
none: undefined,
|
||||
today: { calendar: { period: "day" } },
|
||||
yesterday: { calendar: { period: "day", offset: -1 } },
|
||||
tomorrow: { calendar: { period: "day", offset: 1 } },
|
||||
this_week: { calendar: { period: "week" } },
|
||||
last_week: { calendar: { period: "week", offset: -1 } },
|
||||
next_week: { calendar: { period: "week", offset: 1 } },
|
||||
this_month: { calendar: { period: "month" } },
|
||||
last_month: { calendar: { period: "month", offset: -1 } },
|
||||
next_month: { calendar: { period: "month", offset: 1 } },
|
||||
this_year: { calendar: { period: "year" } },
|
||||
last_year: { calendar: { period: "year", offset: -1 } },
|
||||
next_7d: { calendar: { period: "day", offset: 7 } },
|
||||
next_30d: { calendar: { period: "day", offset: 30 } },
|
||||
} as const;
|
||||
|
||||
@customElement("ha-selector-period")
|
||||
export class HaPeriodSelector extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public selector!: PeriodSelector;
|
||||
|
||||
@property({ attribute: false }) public value?: unknown;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = true;
|
||||
|
||||
private _schema = memoizeOne(
|
||||
(
|
||||
selectedPeriodKey: PeriodKey | undefined,
|
||||
selector: PeriodSelector,
|
||||
localize: LocalizeFunc
|
||||
) =>
|
||||
[
|
||||
{
|
||||
name: "period",
|
||||
required: this.required,
|
||||
selector:
|
||||
selectedPeriodKey && selectedPeriodKey in this._periods(selector)
|
||||
? {
|
||||
select: {
|
||||
multiple: false,
|
||||
options: Object.keys(this._periods(selector)).map(
|
||||
(periodKey) => ({
|
||||
value: periodKey,
|
||||
label:
|
||||
localize(
|
||||
`ui.components.selectors.period.periods.${periodKey as PeriodKey}`
|
||||
) || periodKey,
|
||||
})
|
||||
),
|
||||
},
|
||||
}
|
||||
: { object: {} },
|
||||
},
|
||||
] as const
|
||||
);
|
||||
|
||||
protected render() {
|
||||
const data = this._data(this.value, this.selector);
|
||||
|
||||
const schema = this._schema(
|
||||
typeof data.period === "string" ? (data.period as PeriodKey) : undefined,
|
||||
this.selector,
|
||||
this.hass.localize
|
||||
);
|
||||
|
||||
return html`
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${data}
|
||||
.schema=${schema}
|
||||
.computeHelper=${this._computeHelperCallback}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>
|
||||
`;
|
||||
}
|
||||
|
||||
private _periods = memoizeOne((selector: PeriodSelector) =>
|
||||
Object.fromEntries(
|
||||
Object.entries(PERIODS).filter(([key]) =>
|
||||
selector.period?.options?.includes(key as any)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
private _data = memoizeOne((value: unknown, selector: PeriodSelector) => {
|
||||
for (const [periodKey, period] of Object.entries(this._periods(selector))) {
|
||||
if (deepEqual(period, value)) {
|
||||
return { period: periodKey };
|
||||
}
|
||||
}
|
||||
return { period: value };
|
||||
});
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const newValue = ev.detail.value;
|
||||
|
||||
if (typeof newValue.period === "string") {
|
||||
const periods = this._periods(this.selector);
|
||||
if (newValue.period in periods) {
|
||||
const period = this._periods(this.selector)[newValue.period];
|
||||
fireEvent(this, "value-changed", { value: period });
|
||||
}
|
||||
} else {
|
||||
fireEvent(this, "value-changed", { value: newValue.period });
|
||||
}
|
||||
}
|
||||
|
||||
private _computeHelperCallback = () => this.helper;
|
||||
|
||||
private _computeLabelCallback = () => this.label;
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
position: relative;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-selector-period": HaPeriodSelector;
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,7 @@ const LOAD_ELEMENTS = {
|
||||
number: () => import("./ha-selector-number"),
|
||||
numeric_threshold: () => import("./ha-selector-numeric-threshold"),
|
||||
object: () => import("./ha-selector-object"),
|
||||
period: () => import("./ha-selector-period"),
|
||||
qr_code: () => import("./ha-selector-qr-code"),
|
||||
select: () => import("./ha-selector-select"),
|
||||
selector: () => import("./ha-selector-selector"),
|
||||
|
||||
@@ -60,6 +60,7 @@ export type Selector =
|
||||
| NumberSelector
|
||||
| NumericThresholdSelector
|
||||
| ObjectSelector
|
||||
| PeriodSelector
|
||||
| AssistPipelineSelector
|
||||
| QRCodeSelector
|
||||
| SelectSelector
|
||||
@@ -392,6 +393,27 @@ export interface ObjectSelector {
|
||||
} | null;
|
||||
}
|
||||
|
||||
export type PeriodKey =
|
||||
| "today"
|
||||
| "yesterday"
|
||||
| "tomorrow"
|
||||
| "this_week"
|
||||
| "last_week"
|
||||
| "next_week"
|
||||
| "this_month"
|
||||
| "last_month"
|
||||
| "next_month"
|
||||
| "this_year"
|
||||
| "last_year"
|
||||
| "next_7d"
|
||||
| "next_30d"
|
||||
| "none";
|
||||
export interface PeriodSelector {
|
||||
period: {
|
||||
options: readonly PeriodKey[];
|
||||
} | null;
|
||||
}
|
||||
|
||||
export interface AssistPipelineSelector {
|
||||
assist_pipeline: {
|
||||
include_last_used?: boolean;
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
} from "superstruct";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import { deepEqual } from "../../../../common/util/deep-equal";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
import type { HaFormSchema } from "../../../../components/ha-form/types";
|
||||
import type {
|
||||
@@ -60,17 +59,6 @@ const statTypeMap: Record<(typeof stat_types)[number], StatisticType> = {
|
||||
change: "sum",
|
||||
};
|
||||
|
||||
const periods = {
|
||||
today: { calendar: { period: "day" } },
|
||||
yesterday: { calendar: { period: "day", offset: -1 } },
|
||||
this_week: { calendar: { period: "week" } },
|
||||
last_week: { calendar: { period: "week", offset: -1 } },
|
||||
this_month: { calendar: { period: "month" } },
|
||||
last_month: { calendar: { period: "month", offset: -1 } },
|
||||
this_year: { calendar: { period: "year" } },
|
||||
last_year: { calendar: { period: "year", offset: -1 } },
|
||||
} as const;
|
||||
|
||||
@customElement("hui-statistic-card-editor")
|
||||
export class HuiStatisticCardEditor
|
||||
extends LitElement
|
||||
@@ -109,21 +97,8 @@ export class HuiStatisticCardEditor
|
||||
});
|
||||
}
|
||||
|
||||
private _data = memoizeOne((config: StatisticCardConfig) => {
|
||||
if (!config || !config.period) {
|
||||
return config;
|
||||
}
|
||||
for (const [periodKey, period] of Object.entries(periods)) {
|
||||
if (deepEqual(period, config.period)) {
|
||||
return { ...config, period: periodKey };
|
||||
}
|
||||
}
|
||||
return config;
|
||||
});
|
||||
|
||||
private _schema = memoizeOne(
|
||||
(
|
||||
selectedPeriodKey: string | undefined,
|
||||
localize: LocalizeFunc,
|
||||
enableDateSelect: boolean,
|
||||
metadata?: StatisticsMetaData
|
||||
@@ -153,21 +128,20 @@ export class HuiStatisticCardEditor
|
||||
{
|
||||
name: "period",
|
||||
required: true,
|
||||
selector:
|
||||
selectedPeriodKey && selectedPeriodKey in periods
|
||||
? {
|
||||
select: {
|
||||
multiple: false,
|
||||
options: Object.keys(periods).map((periodKey) => ({
|
||||
value: periodKey,
|
||||
label:
|
||||
localize(
|
||||
`ui.panel.lovelace.editor.card.statistic.periods.${periodKey}`
|
||||
) || periodKey,
|
||||
})),
|
||||
},
|
||||
}
|
||||
: { object: {} },
|
||||
selector: {
|
||||
period: {
|
||||
options: [
|
||||
"today",
|
||||
"yesterday",
|
||||
"this_week",
|
||||
"last_week",
|
||||
"this_month",
|
||||
"last_month",
|
||||
"this_year",
|
||||
"last_year",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
@@ -221,10 +195,9 @@ export class HuiStatisticCardEditor
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const data = this._data(this._config);
|
||||
const data = this._config;
|
||||
|
||||
const schema = this._schema(
|
||||
typeof data.period === "string" ? data.period : undefined,
|
||||
this.hass.localize,
|
||||
!!this._config!.energy_date_selection,
|
||||
this._metadata
|
||||
@@ -255,13 +228,6 @@ export class HuiStatisticCardEditor
|
||||
const config = { ...ev.detail.value } as StatisticCardConfig;
|
||||
Object.keys(config).forEach((k) => config[k] === "" && delete config[k]);
|
||||
|
||||
if (typeof config.period === "string") {
|
||||
const period = periods[config.period];
|
||||
if (period) {
|
||||
config.period = period;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
config.stat_type &&
|
||||
config.entity &&
|
||||
|
||||
@@ -2,15 +2,7 @@ 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,
|
||||
number,
|
||||
object,
|
||||
optional,
|
||||
string,
|
||||
} from "superstruct";
|
||||
import { assert, assign, boolean, object, optional, string } from "superstruct";
|
||||
import { mdiGestureTap } from "@mdi/js";
|
||||
import {
|
||||
ITEM_TAP_ACTION_EDIT,
|
||||
@@ -43,13 +35,7 @@ const cardConfigStruct = assign(
|
||||
hide_section_headers: optional(boolean()),
|
||||
display_order: optional(string()),
|
||||
item_tap_action: optional(string()),
|
||||
due_date_period: optional(
|
||||
object({
|
||||
calendar: optional(
|
||||
object({ period: string(), offset: optional(number()) })
|
||||
),
|
||||
})
|
||||
),
|
||||
due_date_period: optional(object()),
|
||||
})
|
||||
);
|
||||
|
||||
@@ -89,6 +75,24 @@ export class HuiTodoListEditor
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "due_date_period",
|
||||
selector: {
|
||||
period: {
|
||||
options: [
|
||||
"today",
|
||||
"tomorrow",
|
||||
"this_week",
|
||||
"next_week",
|
||||
"this_month",
|
||||
"next_month",
|
||||
"next_7d",
|
||||
"next_30d",
|
||||
"none",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "interactions",
|
||||
type: "expandable",
|
||||
@@ -185,6 +189,7 @@ export class HuiTodoListEditor
|
||||
case "hide_section_headers":
|
||||
case "display_order":
|
||||
case "item_tap_action":
|
||||
case "due_date_period":
|
||||
return this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.card.todo-list.${schema.name}`
|
||||
);
|
||||
@@ -200,6 +205,7 @@ export class HuiTodoListEditor
|
||||
) => {
|
||||
switch (schema.name) {
|
||||
case "hide_section_headers":
|
||||
case "due_date_period":
|
||||
return this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.card.todo-list.${schema.name}_helper`
|
||||
);
|
||||
|
||||
@@ -518,6 +518,24 @@
|
||||
"radius": "[%key:ui::panel::config::zone::detail::radius%]",
|
||||
"radius_meters": "[%key:ui::panel::config::core::section::core::core_config::elevation_meters%]"
|
||||
},
|
||||
"period": {
|
||||
"periods": {
|
||||
"none": "None",
|
||||
"today": "[%key:ui::panel::lovelace::editor::card::statistic::periods::today%]",
|
||||
"tomorrow": "Tomorrow",
|
||||
"yesterday": "[%key:ui::panel::lovelace::editor::card::statistic::periods::yesterday%]",
|
||||
"this_week": "[%key:ui::panel::lovelace::editor::card::statistic::periods::this_week%]",
|
||||
"last_week": "[%key:ui::panel::lovelace::editor::card::statistic::periods::last_week%]",
|
||||
"next_week": "Next week",
|
||||
"this_month": "[%key:ui::panel::lovelace::editor::card::statistic::periods::this_month%]",
|
||||
"last_month": "[%key:ui::panel::lovelace::editor::card::statistic::periods::last_month%]",
|
||||
"next_month": "Next month",
|
||||
"this_year": "[%key:ui::panel::lovelace::editor::card::statistic::periods::this_year%]",
|
||||
"last_year": "[%key:ui::panel::lovelace::editor::card::statistic::periods::last_year%]",
|
||||
"next_7d": "Next 7 days",
|
||||
"next_30d": "Next 30 days"
|
||||
}
|
||||
},
|
||||
"selector": {
|
||||
"options": "Selector options",
|
||||
"type": "Type",
|
||||
@@ -9529,6 +9547,8 @@
|
||||
"hide_section_headers": "Hide section headers",
|
||||
"hide_section_headers_helper": "Removes the 'Active' and 'Completed' section headers and their overflow menus.",
|
||||
"display_order": "Display order",
|
||||
"due_date_period": "Due date",
|
||||
"due_date_period_helper": "Filter items to those which have a due date in the selected period.",
|
||||
"item_tap_action": "Item tap behavior",
|
||||
"actions": {
|
||||
"edit": "Default (edit item)",
|
||||
|
||||
Reference in New Issue
Block a user