import { api } from '../../lib/api.mjs'; const ACTION_ICONS = { status_change: 'refresh-cw', step_complete: 'check-square', step_uncomplete: 'square', resource_assigned: 'user-plus', resource_removed: 'user-minus', photo_upload: 'camera', accounting_update: 'receipt', created: 'plus-circle', updated: 'edit-3', }; const ACTION_LABELS = { status_change: 'Status changed', step_complete: 'Step completed', step_uncomplete: 'Step reopened', resource_assigned: 'Resource assigned', resource_removed: 'Resource removed', photo_upload: 'Photo uploaded', accounting_update: 'Accounting updated', created: 'Work order created', updated: 'Work order updated', }; class WoTimeline extends HTMLElement { #woId = null; #entries = []; #loading = true; static get observedAttributes() { return ['wo-id']; } connectedCallback() { if (!this.shadowRoot) this.attachShadow({ mode: 'open' }); if (this.#woId) this.#load(); } attributeChangedCallback(_, __, val) { this.#woId = val ? +val : null; if (this.shadowRoot && this.#woId) this.#load(); } async #load() { this.#loading = true; this.#render(); try { this.#entries = await api.get(`/work-orders/${this.#woId}/activity`) || []; } catch { this.#entries = []; } this.#loading = false; this.#render(); } #relativeTime(dateStr) { const d = new Date(dateStr); const diff = Date.now() - d.getTime(); const mins = Math.floor(diff / 60000); if (mins < 1) return 'just now'; if (mins < 60) return `${mins}m ago`; const hrs = Math.floor(mins / 60); if (hrs < 24) return `${hrs}h ago`; const days = Math.floor(hrs / 24); if (days < 7) return `${days}d ago`; return d.toLocaleDateString(); } #render() { const s = this.shadowRoot; if (this.#loading) { s.innerHTML = ``; return; } s.innerHTML = ` ${this.#entries.length === 0 ? `

No activity recorded yet

` : `
${this.#entries.map(e => { const icon = ACTION_ICONS[e.action] || 'info'; const label = ACTION_LABELS[e.action] || e.action.replace(/_/g,' '); const cls = e.action.includes('status') ? 'status' : e.action.includes('step') ? 'step' : e.action.includes('photo') ? 'photo' : ''; return `
${this.#esc(label)}
${this.#detailHTML(e)}
`; }).join('')}
`} `; if (window.lucide) lucide.createIcons({ root: s }); s.querySelector('#refresh-btn').addEventListener('click', () => this.#load()); } #detailHTML(e) { if (e.action === 'status_change' && e.old_value && e.new_value) { return `
${this.#esc(e.old_value.replace('_',' '))} → ${this.#esc(e.new_value.replace('_',' '))}
`; } if (e.new_value) { return `
${this.#esc(e.new_value)}
`; } return ''; } #esc(s) { return (s || '').replace(/&/g,'&').replace(//g,'>'); } } customElements.define('wo-timeline', WoTimeline);