// Timeline controller for handling timezone conversion and animations import { Controller } from "@hotwired/stimulus" export default class extends Controller { static targets = ["row", "time", "bar", "timestamp", "date"] static values = { mode: { type: String, default: "timeline" } // "timeline", "events", or "individual" } connect() { if (this.modeValue === "timeline") { this.maxTotal = this.calculateMaxTotal() this.updateTimeline() } else if (this.modeValue === "events") { this.updateEventsTimes() } else { this.updateIndividualTimes() } } calculateMaxTotal() { const totals = this.rowTargets.map(row => parseInt(row.dataset.total)) return Math.max(...totals, 1) } updateTimeline() { this.rowTargets.forEach((row, index) => { this.updateRow(row, index) }) } updateRow(row, index) { const timeIso = row.dataset.timeIso const total = parseInt(row.dataset.total) const timeElement = this.timeTargets.find(target => target.closest('[data-timeline-target="row"]') === row) const barElement = this.barTargets.find(target => target.closest('[data-timeline-target="row"]') === row) // Convert ISO time to local time const date = new Date(timeIso) // Determine if we should show date based on time range const now = new Date() const timeDiff = now - date const hoursDiff = timeDiff / (1000 * 60 * 60) let displayTime if (hoursDiff > 25) { // For periods longer than 25 hours, show date only (no time) displayTime = date.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }) } else { // Check if this is midnight UTC data (daily timeline) vs actual time data (hourly timeline) // Daily timeline: time is at UTC midnight (hours/minutes/seconds = 0) // Hourly timeline: time has actual hours/minutes const utcHours = date.getUTCHours() const utcMinutes = date.getUTCMinutes() const utcSeconds = date.getUTCSeconds() if (utcHours === 0 && utcMinutes === 0 && utcSeconds === 0) { // This is midnight UTC - treat as daily data, show date only displayTime = date.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }) } else { // This is actual time data - show time only displayTime = date.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', hour12: false }) } } timeElement.textContent = displayTime timeElement.title = date.toLocaleString(undefined, { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric', timeZoneName: 'short' }) // Animate the bar width with a slight delay for each row const barWidth = Math.max((total / this.maxTotal) * 100, 5) setTimeout(() => { barElement.style.width = `${barWidth}%` }, 100 + (index * 50)) } updateEventsTimes() { this.timestampTargets.forEach(element => { const iso = element.dataset.iso if (iso) { this.convertToLocalTime(element, iso, "time") } }) this.dateTargets.forEach(element => { const iso = element.dataset.iso if (iso) { this.convertToLocalTime(element, iso, "date") } }) } updateIndividualTimes() { const iso = this.element.dataset.iso if (iso) { this.convertToLocalTime(this.element, iso, this.element.dataset.format || "both") } } convertToLocalTime(element, isoString, format) { const date = new Date(isoString) switch (format) { case "time": element.textContent = date.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }) element.title = date.toLocaleString(undefined, { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit', timeZoneName: 'short' }) break case "date": element.textContent = date.toLocaleDateString(undefined, { year: 'numeric', month: '2-digit', day: '2-digit' }) element.title = date.toLocaleString(undefined, { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit', timeZoneName: 'short' }) break case "both": element.textContent = date.toLocaleString(undefined, { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }) element.title = date.toLocaleString(undefined, { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit', timeZoneName: 'short' }) break } } }