import { api } from '../../lib/api.mjs';
const PHASES = ['all', 'before', 'during', 'after'];
class WoPhotoPanel extends HTMLElement {
#woId = null;
#photos = [];
#phase = 'all';
#loading = true;
#lightboxIdx = -1;
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.#photos = await api.get(`/work-orders/${this.#woId}/attachments`) || []; }
catch { this.#photos = []; }
this.#loading = false;
this.#render();
}
async #upload(files, phase, caption) {
const uploads = Array.from(files).map(async file => {
const form = new FormData();
form.append('file', file);
form.append('phase', phase);
form.append('caption', caption || '');
if (navigator.geolocation) {
await new Promise(res => navigator.geolocation.getCurrentPosition(
pos => { form.append('lat', pos.coords.latitude); form.append('lng', pos.coords.longitude); res(); },
() => res(), { timeout: 3000 }
));
}
return api.upload(`/work-orders/${this.#woId}/attachments`, form);
});
try {
await Promise.all(uploads);
window.dispatchEvent(new CustomEvent('wo:toast', { detail: { message: `${files.length} photo(s) uploaded`, type: 'success' } }));
await this.#load();
} catch (err) {
window.dispatchEvent(new CustomEvent('wo:toast', { detail: { message: err.message, type: 'error' } }));
}
}
async #deletePhoto(id) {
try {
await api.delete(`/work-orders/${this.#woId}/attachments/${id}`);
await this.#load();
} catch (err) {
window.dispatchEvent(new CustomEvent('wo:toast', { detail: { message: err.message, type: 'error' } }));
}
}
#filteredPhotos() {
if (this.#phase === 'all') return this.#photos;
return this.#photos.filter(p => p.phase === this.#phase);
}
#render() {
const s = this.shadowRoot;
if (this.#loading) {
s.innerHTML = ``;
return;
}
const filtered = this.#filteredPhotos();
const phaseCounts = { all: this.#photos.length, before: 0, during: 0, after: 0 };
this.#photos.forEach(p => { if (phaseCounts[p.phase] !== undefined) phaseCounts[p.phase]++; });
s.innerHTML = `
${PHASES.map(ph => `
`).join('')}
${filtered.length === 0
? `
${this.#phase === 'all' ? 'No photos yet' : `No ${this.#phase} photos yet`}
`
: `
${filtered.map((p, i) => `
${p.caption ? `
${this.#esc(p.caption)}
` : ''}
${p.phase ? `
${p.phase}
` : ''}
`).join('')}
`}
`;
if (window.lucide) lucide.createIcons({ root: s });
this.#bindEvents(filtered);
}
#bindEvents(filtered) {
const s = this.shadowRoot;
s.querySelectorAll('.phase-tab').forEach(tab =>
tab.addEventListener('click', () => { this.#phase = tab.dataset.phase; this.#render(); }));
s.querySelectorAll('.photo-tile').forEach(tile =>
tile.addEventListener('click', e => {
if (e.target.closest('.del-btn')) return;
this.#openLightbox(+tile.dataset.idx, filtered);
}));
s.querySelectorAll('.del-btn').forEach(btn =>
btn.addEventListener('click', e => { e.stopPropagation(); this.#deletePhoto(+btn.dataset.id); }));
const dialog = s.querySelector('#upload-dialog');
const fileInput = s.querySelector('#file-input');
const dropZone = s.querySelector('#drop-zone');
const preview = s.querySelector('#preview-list');
const uploadBtn = s.querySelector('#dlg-upload');
s.querySelector('#upload-btn').addEventListener('click', () => {
s.querySelector('#phase-select').value = this.#phase !== 'all' ? this.#phase : '';
s.querySelector('#caption-input').value = '';
preview.innerHTML = '';
uploadBtn.disabled = true;
dialog.showModal();
});
s.querySelector('#dlg-close').addEventListener('click', () => dialog.close());
s.querySelector('#dlg-cancel').addEventListener('click', () => dialog.close());
dropZone.addEventListener('click', () => fileInput.click());
dropZone.addEventListener('dragover', e => { e.preventDefault(); dropZone.classList.add('drag-over'); });
dropZone.addEventListener('dragleave', () => dropZone.classList.remove('drag-over'));
dropZone.addEventListener('drop', e => {
e.preventDefault();
dropZone.classList.remove('drag-over');
this.#setFiles(e.dataTransfer.files, preview, uploadBtn);
});
fileInput.addEventListener('change', () => this.#setFiles(fileInput.files, preview, uploadBtn));
s.querySelector('#dlg-upload').addEventListener('click', () => {
const files = fileInput.files;
const phase = s.querySelector('#phase-select').value;
const caption = s.querySelector('#caption-input').value;
dialog.close();
this.#upload(files, phase, caption);
});
}
#setFiles(files, preview, btn) {
preview.innerHTML = Array.from(files).map(f =>
` ${this.#esc(f.name)}
`
).join('');
if (window.lucide) lucide.createIcons({ root: preview });
btn.disabled = files.length === 0;
}
#openLightbox(idx, photos) {
const s = this.shadowRoot;
const show = (i) => {
const existing = s.querySelector('.lightbox');
if (existing) existing.remove();
if (i < 0 || i >= photos.length) return;
const p = photos[i];
const lb = document.createElement('div');
lb.className = 'lightbox';
lb.innerHTML = `
${photos.length > 1 ? `
` : ''}
${p.caption ? `${this.#esc(p.caption)}
` : ''}`;
s.appendChild(lb);
if (window.lucide) lucide.createIcons({ root: lb });
lb.querySelector('.lightbox-close').addEventListener('click', () => lb.remove());
lb.addEventListener('click', e => { if (e.target === lb) lb.remove(); });
lb.querySelector('.lightbox-prev')?.addEventListener('click', e => { e.stopPropagation(); show(i - 1); });
lb.querySelector('.lightbox-next')?.addEventListener('click', e => { e.stopPropagation(); show(i + 1); });
};
show(idx);
}
#esc(s) { return (s || '').replace(/&/g,'&').replace(//g,'>'); }
}
customElements.define('wo-photo-panel', WoPhotoPanel);