Last active
September 4, 2024 17:18
-
-
Save sam2332/3c7c9730a39a1990d193bd3540a353c4 to your computer and use it in GitHub Desktop.
Home Assistant Trip Card
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type: custom:trips-card | |
title: Lily Trips | |
person: person.lily | |
homeZone: home | |
hours: 24 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import "https://unpkg.com/[email protected]/wired-card.js?module"; | |
import "https://unpkg.com/[email protected]/wired-toggle.js?module"; | |
import { LitElement, html, css } from "https://unpkg.com/[email protected]/lit-element.js?module"; | |
class TripsCard extends LitElement { | |
static get properties() { | |
return { | |
person: { type: String }, | |
homeZone: { type: String }, | |
hours: { type: Number }, | |
trips: { type: Array } | |
}; | |
} | |
constructor() { | |
super(); | |
this.trips = []; | |
} | |
setConfig(config) { | |
if (!config.person) { | |
throw new Error("The 'person' configuration is required"); | |
} | |
if (!config.homeZone) { | |
throw new Error("The 'homeZone' configuration is required"); | |
} | |
this.person = config.person; | |
this.homeZone = config.homeZone; | |
this.hours = config.hours || 24; // Default to 24 hours if not specified | |
this.title = config.title || 'Trips'; | |
} | |
async connectedCallback() { | |
super.connectedCallback(); | |
await this.fetchHistory(); | |
} | |
async fetchHistory() { | |
const endDate = new Date(); | |
const startDate = new Date(endDate.getTime() - this.hours * 3600000); | |
const response = await this.hass.callApi('GET', `history/period/${startDate.toISOString()}?filter_entity_id=${this.person}`); | |
this.processTrips(response); | |
} | |
processTrips(data) { | |
let lastStateWasHome = false; | |
let currentTrip = null; | |
this.trips = []; | |
data[0].forEach(entry => { | |
const currentStateIsHome = entry.state === this.homeZone; | |
if (!currentStateIsHome && lastStateWasHome) { | |
currentTrip = { start: entry.last_changed, stops: [] }; | |
} else if (currentStateIsHome && currentTrip) { | |
currentTrip.end = entry.last_changed; | |
this.trips.push(currentTrip); | |
currentTrip = null; | |
} | |
lastStateWasHome = currentStateIsHome; | |
}); | |
if (currentTrip) { | |
currentTrip.end = "Ongoing"; | |
this.trips.push(currentTrip); | |
} | |
this.requestUpdate(); | |
} | |
formatDate(dateString) { | |
const date = new Date(dateString); | |
let hours = date.getHours(); | |
let minutes = date.getMinutes(); | |
const ampm = hours >= 12 ? 'PM' : 'AM'; | |
hours = hours % 12; | |
hours = hours ? hours : 12; // the hour '0' should be '12' | |
minutes = minutes < 10 ? '0' + minutes : minutes; | |
return `${hours}:${minutes} ${ampm}`; | |
} | |
groupTripsByDate() { | |
const tripsByDate = new Map(); | |
this.trips.forEach(trip => { | |
const startDate = new Date(trip.start).toLocaleDateString(); | |
if (!tripsByDate.has(startDate)) { | |
tripsByDate.set(startDate, []); | |
} | |
tripsByDate.get(startDate).push(trip); | |
}); | |
return tripsByDate; | |
} | |
formatTime(dateString) { | |
const date = new Date(dateString); | |
let hours = date.getHours(); | |
let minutes = date.getMinutes(); | |
const ampm = hours >= 12 ? 'PM' : 'AM'; | |
hours = hours % 12; | |
hours = hours ? hours : 12; // the hour '0' should be '12' | |
minutes = minutes < 10 ? '0' + minutes : minutes; | |
return `${hours}:${minutes} ${ampm}`; | |
} | |
render() { | |
const groupedTrips = this.groupTripsByDate(); // Get trips grouped by date | |
// Convert the Map to an array and sort by date in descending order | |
const sortedDates = Array.from(groupedTrips).sort((a, b) => new Date(b[0]) - new Date(a[0])); | |
return html` | |
<ha-card> | |
<div class="card-header"> | |
<div class="name" style="cursor: pointer;">${this.title}</div> | |
</div> | |
<div class="card-content"> | |
${sortedDates.map(([date, trips]) => html` | |
<div>${date}</div> | |
<ul> | |
${trips.map(trip => html` | |
<li @click="${() => this.selectTrip(trip)}"> | |
${this.formatTime(trip.start)} - ${trip.end ? this.formatTime(trip.end) : 'Ongoing'} | |
</li> | |
`)} | |
</ul> | |
`)} | |
</div> | |
</ha-card> | |
`; | |
} | |
selectTrip(trip) { | |
if (!trip.start) return; // Guard clause if no start time is available | |
const baseUrl = `${window.location.origin}/logbook`; | |
const startTime = new Date(trip.start).toISOString(); | |
const endTime = trip.end ? new Date(trip.end).toISOString() : new Date().toISOString(); | |
// Here, end_date is set exactly to end_time, not a day later | |
const logbookUrl = `${baseUrl}?entity_id=${encodeURIComponent(this.person)}&start_time=${encodeURIComponent(startTime)}&end_time=${encodeURIComponent(endTime)}&start_date=${encodeURIComponent(startTime)}&end_date=${encodeURIComponent(endTime)}`; | |
// Open in a new tab | |
window.open(logbookUrl, '_blank'); | |
} | |
calculateDuration(trip) { | |
if (!trip.end || trip.end === "Ongoing") { | |
return "Ongoing"; | |
} | |
const startDate = new Date(trip.start); | |
const endDate = new Date(trip.end); | |
return `${Math.round((endDate - startDate) / 60000)} minutes`; // Duration in minutes | |
} | |
} | |
customElements.define('trips-card', TripsCard); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment