import { api } from '../../lib/api.mjs';
const PRIORITIES = ['low', 'normal', 'high', 'urgent'];
const STEP_TYPES = [
{ value: 'work_step', label: 'Work Step', icon: 'check-square', color: '#0A7EA4', hint: '' },
{ value: 'photo', label: 'Photo', icon: 'camera', color: '#8B5CF6', hint: 'Requires a photo to complete' },
{ value: 'inspection', label: 'Inspect', icon: 'clipboard-check', color: '#E07B39', hint: 'Pass / Fail / N/A' },
{ value: 'note', label: 'Note', icon: 'file-text', color: '#64748B', hint: 'Free-text entry' },
];
class ProfileForm extends HTMLElement {
#onSave = null;
connectedCallback() {
if (!this.shadowRoot) {
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = ``;
}
}
open(profile, onSave) {
this.#onSave = onSave;
const isEdit = !!profile;
const d = this.shadowRoot.querySelector('dialog');
d.innerHTML = `
${isEdit ? `
Active
` : ''}
Default Steps
${(profile?.steps || []).length === 0
? '
No steps yet — add steps below
'
: (profile?.steps || []).map((s, i) => this.#stepRowHTML(s, i)).join('')}
${STEP_TYPES.map(t => `
`).join('')}
`;
if (window.lucide) lucide.createIcons({ root: d });
const profileId = profile?.id || null;
let pendingSteps = (profile?.steps || []).map(s => ({ ...s }));
let selectedType = 'work_step';
// ── Step list rendering ───────────────────────────────────────────────
const renderSteps = () => {
const list = d.querySelector('#step-list');
list.innerHTML = pendingSteps.length === 0
? 'No steps yet — add steps below
'
: pendingSteps.map((s, i) => this.#stepRowHTML(s, i)).join('');
if (window.lucide) lucide.createIcons({ root: list });
list.querySelectorAll('.step-title-input').forEach((inp, i) => {
inp.addEventListener('input', () => { pendingSteps[i].title = inp.value; });
inp.addEventListener('change', () => { pendingSteps[i].title = inp.value; });
});
list.querySelectorAll('.step-req-check').forEach((cb, i) => {
cb.addEventListener('change', () => { pendingSteps[i].required = cb.checked; });
});
list.querySelectorAll('.step-del').forEach((btn, i) => {
btn.addEventListener('click', () => { pendingSteps.splice(i, 1); renderSteps(); });
});
};
renderSteps();
// ── Type picker ───────────────────────────────────────────────────────
const cfgDiv = d.querySelector('#cfg-fields');
const renderCfgFields = (type) => {
if (type === 'work_step') {
cfgDiv.style.display = 'none';
cfgDiv.innerHTML = '';
return;
}
cfgDiv.style.display = 'block';
if (type === 'photo') {
cfgDiv.innerHTML = `
`;
} else if (type === 'inspection') {
cfgDiv.innerHTML = `
`;
} else if (type === 'note') {
cfgDiv.innerHTML = `
`;
}
};
const getCfgJSON = (type) => {
if (type === 'photo') {
const phase = cfgDiv.querySelector('#cfg-phase')?.value || 'during';
const caption = cfgDiv.querySelector('#cfg-caption')?.value.trim() || '';
return JSON.stringify({ phase, caption_prompt: caption });
}
if (type === 'inspection') {
const criteria = cfgDiv.querySelector('#cfg-criteria')?.value.trim() || '';
return criteria ? JSON.stringify({ criteria }) : '';
}
if (type === 'note') {
const prompt = cfgDiv.querySelector('#cfg-prompt')?.value.trim() || '';
return prompt ? JSON.stringify({ prompt }) : '';
}
return '';
};
d.querySelectorAll('.type-btn').forEach(btn => {
btn.addEventListener('click', () => {
selectedType = btn.dataset.type;
d.querySelectorAll('.type-btn').forEach(b => b.classList.toggle('active', b === btn));
renderCfgFields(selectedType);
});
});
// ── Add step ──────────────────────────────────────────────────────────
d.querySelector('#add-step-btn').addEventListener('click', () => {
const inp = d.querySelector('#new-step-title');
const title = inp.value.trim();
if (!title) { inp.focus(); return; }
pendingSteps.push({
id: null,
step_order: pendingSteps.length + 1,
title,
description: '',
required: true,
step_type: selectedType,
type_config: getCfgJSON(selectedType),
});
inp.value = '';
// Reset type picker to work_step after adding
selectedType = 'work_step';
d.querySelectorAll('.type-btn').forEach(b => b.classList.toggle('active', b.dataset.type === 'work_step'));
renderCfgFields('work_step');
renderSteps();
});
d.querySelector('#new-step-title').addEventListener('keydown', e => {
if (e.key === 'Enter') { e.preventDefault(); d.querySelector('#add-step-btn').click(); }
});
// ── Dialog controls ───────────────────────────────────────────────────
d.querySelector('#dlg-close').addEventListener('click', () => d.close());
d.querySelector('#dlg-cancel').addEventListener('click', () => d.close());
d.querySelector('#dlg-save').addEventListener('click', async () => {
const name = d.querySelector('#p-name').value.trim();
const errEl = d.querySelector('#form-error');
if (!name) { errEl.textContent = 'Name is required'; d.querySelector('#p-name').focus(); return; }
errEl.textContent = '';
const saveBtn = d.querySelector('#dlg-save');
saveBtn.disabled = true;
saveBtn.textContent = 'Saving…';
const payload = {
name,
description: d.querySelector('#p-desc').value.trim(),
category: d.querySelector('#p-cat').value.trim(),
default_priority: d.querySelector('#p-pri').value,
default_duration_hours: d.querySelector('#p-dur').value ? +d.querySelector('#p-dur').value : null,
default_instructions: d.querySelector('#p-instr').value.trim(),
active: isEdit ? d.querySelector('#p-active').checked : true,
};
try {
let savedProfile;
if (isEdit) {
savedProfile = await api.put(`/profiles/${profile.id}`, payload);
} else {
savedProfile = await api.post('/profiles', payload);
}
const savedId = savedProfile.id;
const origIds = new Set((profile?.steps || []).map(s => s.id));
// Delete removed steps
for (const orig of (profile?.steps || [])) {
if (!pendingSteps.find(s => s.id === orig.id)) {
await api.delete(`/profiles/${savedId}/steps/${orig.id}`);
}
}
// Update existing / create new
for (let i = 0; i < pendingSteps.length; i++) {
const s = pendingSteps[i];
const stepPayload = {
title: s.title,
description: s.description || '',
required: s.required !== false,
step_order: i + 1,
step_type: s.step_type || 'work_step',
type_config: s.type_config || '',
};
if (s.id && origIds.has(s.id)) {
await api.put(`/profiles/${savedId}/steps/${s.id}`, stepPayload);
} else {
await api.post(`/profiles/${savedId}/steps`, stepPayload);
}
}
d.close();
window.dispatchEvent(new CustomEvent('wo:toast', {
detail: { message: isEdit ? 'Profile updated' : 'Profile created', type: 'success' }
}));
this.#onSave?.();
} catch (err) {
errEl.textContent = err.message;
saveBtn.disabled = false;
saveBtn.textContent = isEdit ? 'Save Changes' : 'Create Profile';
}
});
d.showModal();
d.querySelector('#p-name').focus();
}
#stepRowHTML(s, i) {
const type = s.step_type || 'work_step';
const tdef = { work_step: { color: '#0A7EA4', label: 'Step' }, photo: { color: '#8B5CF6', label: 'Photo' }, inspection: { color: '#E07B39', label: 'Inspect' }, note: { color: '#64748B', label: 'Note' } };
const td = tdef[type] || tdef.work_step;
const cfg = this.#parseCfg(s.type_config);
const cfgSummary = type === 'photo' ? (cfg.phase ? cfg.phase + (cfg.caption_prompt ? ' · ' + cfg.caption_prompt : '') : '')
: type === 'inspection' ? (cfg.criteria || '')
: type === 'note' ? (cfg.prompt || '')
: '';
return `
${i + 1}
${td.label}
${cfgSummary ? `${this.#esc(cfgSummary)}` : ''}
`;
}
#parseCfg(str) { try { return JSON.parse(str || '{}'); } catch { return {}; } }
#esc(s) { return (s || '').replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); }
}
customElements.define('profile-form', ProfileForm);