diff --git a/src/components/ha-date-input.ts b/src/components/ha-date-input.ts index f5386b81e0..73e7f9fa3e 100644 --- a/src/components/ha-date-input.ts +++ b/src/components/ha-date-input.ts @@ -54,6 +54,7 @@ export class HaDateInput extends LitElement { .disabled=${this.disabled} iconTrailing helperPersistent + readonly @click=${this._openDialog} .value=${this.value ? formatDateNumeric(new Date(this.value), this.locale) diff --git a/src/panels/calendar/dialog-calendar-event-detail.ts b/src/panels/calendar/dialog-calendar-event-detail.ts index 96bdac6df8..cf3db9adb5 100644 --- a/src/panels/calendar/dialog-calendar-event-detail.ts +++ b/src/panels/calendar/dialog-calendar-event-detail.ts @@ -1,6 +1,6 @@ import "@material/mwc-button"; import { mdiCalendarClock, mdiClose } from "@mdi/js"; -import { isSameDay } from "date-fns/esm"; +import { addDays, isSameDay } from "date-fns/esm"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { property, state } from "lit/decorators"; import { RRule } from "rrule"; @@ -134,7 +134,10 @@ class DialogCalendarEventDetail extends LitElement { private _formatDateRange() { const start = new Date(this._data!.dtstart); - const end = new Date(this._data!.dtend); + // All day events should be displayed as a day earlier + const end = isDate(this._data.dtend) + ? addDays(new Date(this._data!.dtend), -1) + : new Date(this._data!.dtend); // The range can be shortened when the start and end are on the same day. if (isSameDay(start, end)) { if (isDate(this._data.dtstart)) { @@ -148,10 +151,15 @@ class DialogCalendarEventDetail extends LitElement { )} - ${formatTime(end, this.hass.locale)}`; } // An event across multiple dates, optionally with a time range - return `${formatDateTime(start, this.hass.locale)} - ${formatDateTime( - end, - this.hass.locale - )}`; + return `${ + isDate(this._data.dtstart) + ? formatDate(start, this.hass.locale) + : formatDateTime(start, this.hass.locale) + } - ${ + isDate(this._data.dtend) + ? formatDate(end, this.hass.locale) + : formatDateTime(end, this.hass.locale) + }`; } private async _editEvent() { diff --git a/src/panels/calendar/dialog-calendar-event-editor.ts b/src/panels/calendar/dialog-calendar-event-editor.ts index 03ddcdd3d9..790312570f 100644 --- a/src/panels/calendar/dialog-calendar-event-editor.ts +++ b/src/panels/calendar/dialog-calendar-event-editor.ts @@ -4,6 +4,7 @@ import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit"; import { addDays, addHours, startOfHour } from "date-fns/esm"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; +import memoizeOne from "memoize-one"; import { isDate } from "../../common/string/is_date"; import "../../components/ha-date-input"; import "../../components/ha-time-input"; @@ -39,7 +40,9 @@ class DialogCalendarEventEditor extends LitElement { @state() private _calendarId?: string; - @state() private _data?: CalendarEventMutableParams; + @state() private _summary = ""; + + @state() private _rrule?: string; @state() private _allDay = false; @@ -49,40 +52,30 @@ class DialogCalendarEventEditor extends LitElement { @state() private _submitting = false; - public async showDialog( - params: CalendarEventEditDialogParams - ): Promise { + public showDialog(params: CalendarEventEditDialogParams): void { this._error = undefined; this._params = params; this._calendars = params.calendars; this._calendarId = params.calendarId || this._calendars[0].entity_id; if (params.entry) { const entry = params.entry!; - this._data = entry; this._allDay = isDate(entry.dtstart); + this._summary = entry.summary; + this._rrule = entry.rrule; if (this._allDay) { this._dtstart = new Date(entry.dtstart); // Calendar event end dates are exclusive, but not shown that way in the UI. The // reverse happens when persisting the event. - this._dtend = new Date(entry.dtend); - this._dtend.setDate(this._dtend.getDate() - 1); + this._dtend = addDays(new Date(entry.dtend), -1); } else { this._dtstart = new Date(entry.dtstart); this._dtend = new Date(entry.dtend); } } else { - this._data = { - summary: "", - // Dates are set in _dateChanged() - dtstart: "", - dtend: "", - }; this._allDay = false; this._dtstart = startOfHour(new Date()); this._dtend = addHours(this._dtstart, 1); - this._dateChanged(); } - await this.updateComplete; } protected render(): TemplateResult { @@ -90,6 +83,12 @@ class DialogCalendarEventEditor extends LitElement { return html``; } const isCreate = this._params.entry === undefined; + + const { startDate, startTime, endDate, endTime } = this._getLocaleStrings( + this._dtstart, + this._dtend + ); + return html` ${isCreate ? this.hass.localize("ui.components.calendar.event.add") - : this._data!.summary} + : this._summary}
${!this._allDay ? html`` @@ -174,14 +173,14 @@ class DialogCalendarEventEditor extends LitElement { >
${!this._allDay ? html`` @@ -190,7 +189,7 @@ class DialogCalendarEventEditor extends LitElement {
@@ -230,57 +229,78 @@ class DialogCalendarEventEditor extends LitElement { `; } + private _getLocaleStrings = memoizeOne((startDate?: Date, endDate?: Date) => + // en-CA locale used for date format YYYY-MM-DD + // en-GB locale used for 24h time format HH:MM:SS + { + const timeZone = this.hass.config.time_zone; + return { + startDate: startDate?.toLocaleDateString("en-CA", { timeZone }), + startTime: startDate?.toLocaleTimeString("en-GB", { timeZone }), + endDate: endDate?.toLocaleDateString("en-CA", { timeZone }), + endTime: endDate?.toLocaleTimeString("en-GB", { timeZone }), + }; + } + ); + private _handleSummaryChanged(ev) { - this._data!.summary = ev.target.value; + this._summary = ev.target.value; } private _handleRRuleChanged(ev) { - this._data!.rrule = ev.detail.value; - this.requestUpdate(); + this._rrule = ev.detail.value; } private _allDayToggleChanged(ev) { this._allDay = ev.target.checked; - this._dateChanged(); } private _startDateChanged(ev: CustomEvent) { this._dtstart = new Date( ev.detail.value + "T" + this._dtstart!.toISOString().split("T")[1] ); - this._dateChanged(); } private _endDateChanged(ev: CustomEvent) { this._dtend = new Date( ev.detail.value + "T" + this._dtend!.toISOString().split("T")[1] ); - this._dateChanged(); } private _startTimeChanged(ev: CustomEvent) { this._dtstart = new Date( this._dtstart!.toISOString().split("T")[0] + "T" + ev.detail.value ); - this._dateChanged(); } private _endTimeChanged(ev: CustomEvent) { this._dtend = new Date( this._dtend!.toISOString().split("T")[0] + "T" + ev.detail.value ); - this._dateChanged(); } - private _dateChanged() { + private _calculateData() { + const { startDate, startTime, endDate, endTime } = this._getLocaleStrings( + this._dtstart, + this._dtend + ); + const data: CalendarEventMutableParams = { + summary: this._summary, + rrule: this._rrule, + dtstart: "", + dtend: "", + }; if (this._allDay) { - this._data!.dtstart = this._dtstart!.toISOString(); + data.dtstart = startDate!; // End date/time is exclusive when persisted - this._data!.dtend = addDays(new Date(this._dtend!), 1).toISOString(); + data.dtend = addDays(new Date(this._dtend!), 1).toLocaleDateString( + "en-CA" + ); } else { - this._data!.dtstart = this._dtstart!.toISOString(); - this._data!.dtend = this._dtend!.toISOString(); + data.dtstart = `${startDate}T${startTime}`; + data.dtend = `${endDate}T${endTime}`; } + return data; } private _handleCalendarChanged(ev: CustomEvent) { @@ -290,7 +310,11 @@ class DialogCalendarEventEditor extends LitElement { private async _createEvent() { this._submitting = true; try { - await createCalendarEvent(this.hass!, this._calendarId!, this._data!); + await createCalendarEvent( + this.hass!, + this._calendarId!, + this._calculateData() + ); } catch (err: any) { this._error = err ? err.message : "Unknown error"; } finally { @@ -358,9 +382,10 @@ class DialogCalendarEventEditor extends LitElement { this._calendars = []; this._calendarId = undefined; this._params = undefined; - this._data = undefined; this._dtstart = undefined; this._dtend = undefined; + this._summary = ""; + this._rrule = undefined; } static get styles(): CSSResultGroup { diff --git a/src/panels/calendar/ha-recurrence-rule-editor.ts b/src/panels/calendar/ha-recurrence-rule-editor.ts index 6c093e9724..b2c97a9aaa 100644 --- a/src/panels/calendar/ha-recurrence-rule-editor.ts +++ b/src/panels/calendar/ha-recurrence-rule-editor.ts @@ -53,19 +53,22 @@ export class RecurrenceRuleEditor extends LitElement { protected willUpdate(changedProps: PropertyValues) { super.willUpdate(changedProps); - if (!changedProps.has("value") && !changedProps.has("locale")) { + if (changedProps.has("locale")) { + this._allWeekdays = getWeekdays(firstWeekdayIndex(this.locale)).map( + (day: Weekday) => day.toString() as WeekdayStr + ); + } + + if (!changedProps.has("value") || this._computedRRule === this.value) { return; } + this._interval = 1; this._weekday.clear(); this._end = "never"; this._count = undefined; this._until = undefined; - this._allWeekdays = getWeekdays(firstWeekdayIndex(this.locale)).map( - (day: Weekday) => day.toString() as WeekdayStr - ); - this._computedRRule = this.value; if (this.value === "") { this._freq = "none"; @@ -274,6 +277,7 @@ export class RecurrenceRuleEditor extends LitElement { } private _onUntilChange(e: CustomEvent) { + e.stopPropagation(); this._until = new Date(e.detail.value); this._updateRule(); } diff --git a/src/panels/calendar/recurrence.ts b/src/panels/calendar/recurrence.ts index 204efafa31..dce91b7d6b 100644 --- a/src/panels/calendar/recurrence.ts +++ b/src/panels/calendar/recurrence.ts @@ -2,6 +2,7 @@ // and the values defined by rrule.js. import { RRule, Frequency, Weekday } from "rrule"; import type { WeekdayStr } from "rrule"; +import { addDays, addMonths, addWeeks, addYears } from "date-fns"; export type RepeatFrequency = | "none" @@ -35,14 +36,14 @@ export function untilValue(freq: RepeatFrequency): Date { const increment = DEFAULT_COUNT[freq]; switch (freq) { case "yearly": - return new Date(new Date().setFullYear(today.getFullYear() + increment)); + return addYears(today, increment); case "monthly": - return new Date(new Date().setMonth(today.getMonth() + increment)); + return addMonths(today, increment); case "weekly": - return new Date(new Date().setDate(today.getDate() + 7 * increment)); + return addWeeks(today, increment); case "daily": default: - return new Date(new Date().setDate(today.getDate() + increment)); + return addDays(today, increment); } }