// ============================================================
// Checklists Module
// Views: Templates, Instances, Builder, Fill-Out, Job Types
// ============================================================
let clState = {
tab: 'templates',
tPage: 1, tSearch: '', tType: '', tActive: '1',
iPage: 1, iStatus: '', iType: '', iDateFrom: '', iDateTo: '',
};
let _clJobTypes = null;
let _clVehicles = null;
let _clUsers = null;
// ── Entry Point ──────────────────────────────────────────────
async function renderChecklists(params = {}) {
if (params.instance_id) return renderInstanceFillOut(params.instance_id);
const content = document.getElementById('page-content');
const canManage = Auth.can('checklists', 'create') || Auth.can('checklists', 'edit');
content.innerHTML = `
`;
await _loadClCaches();
renderChecklistsTab(clState.tab);
}
async function _loadClCaches() {
const [jt, veh, usr] = await Promise.all([
_clJobTypes ? Promise.resolve({ success: true, data: { types: _clJobTypes } }) : API.post('jobcard_types/list', {}),
_clVehicles ? Promise.resolve({ success: true, data: { vehicles: _clVehicles } }) : API.post('fleet/vehicles', { action: 'list' }),
_clUsers ? Promise.resolve({ success: true, data: { users: _clUsers } }) : API.post('auth/users_list', {}),
]);
if (jt.success) _clJobTypes = jt.data.types || [];
if (veh.success) _clVehicles = veh.data.vehicles || [];
if (usr.success) _clUsers = usr.data.users || [];
}
function renderChecklistsTab(tab) {
clState.tab = tab;
document.querySelectorAll('#cl-tabs .tab-btn').forEach(b => {
b.classList.toggle('active', b.dataset.tab === tab);
});
const wrap = document.getElementById('cl-tab-content');
if (!wrap) return;
wrap.innerHTML = '';
if (tab === 'templates') loadTemplatesList();
if (tab === 'instances') loadInstancesList();
}
// ── TEMPLATES LIST ───────────────────────────────────────────
async function loadTemplatesList() {
const wrap = document.getElementById('cl-tab-content');
if (!wrap) return;
const canManage = Auth.can('checklists', 'create') || Auth.can('checklists', 'edit');
wrap.innerHTML = `
`;
await _fetchTemplates();
}
const clTSearchDebounced = debounce(val => { clState.tSearch = val; _fetchTemplates(); }, 350);
async function _fetchTemplates() {
const body = document.getElementById('cl-templates-body');
if (!body) return;
const canManage = Auth.can('checklists', 'create') || Auth.can('checklists', 'edit');
const res = await API.post('checklists/template_list', {
page: clState.tPage, limit: 20,
search: clState.tSearch, linked_type: clState.tType, is_active: clState.tActive,
});
if (!res.success) { body.innerHTML = `${res.message}
`; return; }
const templates = res.data.templates || [];
if (!templates.length) {
body.innerHTML = `📋
No templates found
Create your first checklist template.
${canManage ? `
` : ''}
`;
document.getElementById('cl-t-pagination').innerHTML = '';
return;
}
const typeIcon = { general: '📂', fleet: '🚗', job_card: '📋' };
const typeLabel = { general: 'General', fleet: 'Fleet', job_card: 'Job Card' };
const freqLabel = { once: 'Once', daily: 'Daily', weekly: 'Weekly', monthly: 'Monthly', custom: 'Custom' };
if (window.innerWidth < 768) {
body.innerHTML = `
${templates.map(t => `
${clH(t.name)}
${t.description ? `
${clH(t.description)}
` : ''}
${t.vehicle_reg ? `
🚗 ${clH(t.vehicle_reg)} — ${clH(t.vehicle_make || '')} ${clH(t.vehicle_model || '')}
` : ''}
${typeIcon[t.linked_type] || ''} ${typeLabel[t.linked_type] || t.linked_type}
${freqLabel[t.frequency] || t.frequency || 'Once'}
${t.item_count || 0} Qs · ${t.instance_count || 0} submissions
${t.is_active ? 'Active' : 'Inactive'}
${t.pending_count > 0 ? `${t.pending_count} pending` : ''}
${canManage ? `
` : ''}
${t.linked_type !== 'job_card' ? `
📄 PDF` : ''}
`).join('')}
`;
} else {
body.innerHTML = `
| Name | Type | Frequency | Questions | Submissions | Pending | Status | |
${templates.map(t => `
|
${clH(t.name)}
${t.description ? `${clH(t.description)} ` : ''}
${t.vehicle_reg ? `🚗 ${clH(t.vehicle_reg)} — ${clH(t.vehicle_make || '')} ${clH(t.vehicle_model || '')} ` : ''}
|
${typeIcon[t.linked_type] || ''} ${typeLabel[t.linked_type] || t.linked_type} |
${freqLabel[t.frequency] || t.frequency || 'Once'} |
${t.item_count || 0} |
${t.instance_count || 0} |
${t.pending_count > 0 ? `${t.pending_count}` : '0'} |
${t.is_active ? 'Active' : 'Inactive'} |
${canManage ? `
` : ''}
${t.linked_type !== 'job_card' ? `📄 Blank PDF` : ''}
|
`).join('')}
`;
}
renderPagination('cl-t-pagination', res.data.pagination, `p => { clState.tPage=p; _fetchTemplates(); }`);
}
// ── INSTANCES LIST ───────────────────────────────────────────
async function loadInstancesList() {
const wrap = document.getElementById('cl-tab-content');
if (!wrap) return;
wrap.innerHTML = `
`;
await _fetchInstances();
}
async function _fetchInstances() {
const body = document.getElementById('cl-instances-body');
if (!body) return;
const res = await API.post('checklists/instance_list', {
page: clState.iPage, limit: 25,
status: clState.iStatus, linked_type: clState.iType,
date_from: clState.iDateFrom, date_to: clState.iDateTo,
});
if (!res.success) { body.innerHTML = `${res.message}
`; return; }
const instances = res.data.instances || [];
if (!instances.length) {
body.innerHTML = ``;
document.getElementById('cl-i-pagination').innerHTML = '';
return;
}
const statusCls = { pending: 'badge-warning', in_progress: 'badge-info', completed: 'badge-success', missed: 'badge-danger' };
if (window.innerWidth < 768) {
body.innerHTML = `
${instances.map(i => {
const pct = i.total_items > 0 ? Math.round((i.answered_items / i.total_items) * 100) : 0;
return `
${clH(i.template_name || '—')}
${Fmt.date(i.due_date)} · ${clH(i.assigned_to_name || 'Unassigned')}
${i.vehicle_reg ? `
🚗 ${clH(i.vehicle_reg)}
` : ''}
${Fmt.capitalize((i.status || '').replace('_', ' '))}
${i.answered_items}/${i.total_items}
${i.status !== 'completed'
? `
`
: `
📄 PDF`}
`;
}).join('')}
`;
} else {
body.innerHTML = `
| Checklist | Type | Due Date | Assigned To | Vehicle | Progress | Status | |
${instances.map(i => {
const pct = i.total_items > 0 ? Math.round((i.answered_items / i.total_items) * 100) : 0;
return `
| ${clH(i.template_name || '—')} |
${i.linked_type || '—'} |
${Fmt.date(i.due_date)}${i.due_time ? ' ' + i.due_time.slice(0, 5) : ''} |
${clH(i.assigned_to_name || '—')} |
${i.vehicle_reg ? clH(i.vehicle_reg) + ' ' + clH(i.vehicle_make || '') : '—'} |
${i.answered_items}/${i.total_items}
|
${Fmt.capitalize((i.status || '').replace('_', ' '))} |
${i.status !== 'completed' ? `` : `📄 PDF`}
|
`;
}).join('')}
`;
}
renderPagination('cl-i-pagination', res.data.pagination, `p => { clState.iPage=p; _fetchInstances(); }`);
}
// ── INSTANCE FILL-OUT ────────────────────────────────────────
async function renderInstanceFillOut(instanceId) {
const content = document.getElementById('page-content');
content.innerHTML = ``;
const res = await API.post('checklists/instance_get', { id: instanceId });
if (!res.success) { Toast.show(res.message, 'error'); Router.navigate('checklists'); return; }
const inst = res.data.instance;
const items = inst.items || [];
const isCompleted = inst.status === 'completed';
// Set BEFORE items.map so _renderImageSection can read it during HTML build
window._clInstanceId = instanceId;
window._clInstanceAnswers = {};
items.forEach(it => {
window._clInstanceAnswers[it.id] = {
answer_bool: it.answer_bool !== null && it.answer_bool !== undefined ? parseInt(it.answer_bool) : null,
answer_text: it.answer_text || '',
note: it.note || '',
images: it.images || [], // array of {id, filename}
};
});
const statusCls = { pending: 'badge-warning', in_progress: 'badge-info', completed: 'badge-success', missed: 'badge-danger' };
content.innerHTML = `
${inst.template_description ? `
${clH(inst.template_description)}
` : ''}
${items.length ? items.map((it, idx) => _renderQuestionCard(it, idx, isCompleted)).join('') : '
This checklist has no questions.
'}
${!isCompleted && items.length ? `
` : ''}
${isCompleted ? `
✓ Submitted by ${clH(inst.completed_by_name || '—')} on ${Fmt.datetime(inst.submitted_at)}
${inst.instance_notes ? `
📝 ${clH(inst.instance_notes)}
` : ''}
` : ''}
`;
}
function _renderQuestionCard(it, idx, isReadOnly) {
const ans = window._clInstanceAnswers?.[it.id] || {};
const isYes = ans.answer_bool === 1;
const isNo = ans.answer_bool === 0;
const images = ans.images || [];
const needsImage = it.requires_image === 'always'
|| (it.requires_image === 'on_yes' && isYes)
|| (it.requires_image === 'on_no' && isNo);
const answerHtml = (() => {
if (it.answer_type === 'yes_no') {
if (isReadOnly) {
return `${isYes ? '✓ Yes' : isNo ? '✕ No' : '— Unanswered'}
`;
}
return `
`;
}
if (it.answer_type === 'text') {
if (isReadOnly) return `${ans.answer_text ? clH(ans.answer_text) : 'No answer'}
`;
return ``;
}
if (it.answer_type === 'number') {
if (isReadOnly) return `${ans.answer_text || 'No answer'}
`;
return ``;
}
return `Photo submission required
`;
})();
const showImgSection = needsImage || images.length > 0 || it.requires_image !== 'never';
const noteHtml = it.answer_type !== 'text' ? (
isReadOnly && ans.note ? `📝 ${clH(ans.note)}
` :
!isReadOnly ? `📝 Add note
` : ''
) : '';
return `
${answerHtml}
${_renderImageSection(it.id, images, isReadOnly, needsImage)}
${noteHtml}
`;
}
function _renderImageSection(itemId, images, isReadOnly, needsImage) {
const instanceId = window._clInstanceId || 0;
const thumbs = images.map(img => `

${!isReadOnly ? `
` : ''}
`).join('');
const addBtn = !isReadOnly ? `
` : (images.length === 0 ? 'No images attached' : '');
return `${thumbs}${addBtn}
`;
}
function clAnswerYN(itemId, val) {
if (!window._clInstanceAnswers) window._clInstanceAnswers = {};
if (!window._clInstanceAnswers[itemId]) window._clInstanceAnswers[itemId] = {};
window._clInstanceAnswers[itemId].answer_bool = val;
const card = document.getElementById(`cl-q-${itemId}`);
if (!card) return;
card.querySelectorAll('.cl-yn-btn').forEach(b => b.classList.remove('active-yes', 'active-no'));
const btns = card.querySelectorAll('.cl-yn-btn');
if (val === 1) btns[0]?.classList.add('active-yes'); else btns[1]?.classList.add('active-no');
card.style.outline = '';
const req = card.dataset.requiresImage;
const imgSec = document.getElementById(`cl-img-section-${itemId}`);
if (imgSec) {
const show = req === 'always' || (req === 'on_yes' && val === 1) || (req === 'on_no' && val === 0);
const hasImages = (window._clInstanceAnswers[itemId]?.images || []).length > 0;
imgSec.style.display = (show || hasImages || req !== 'never') ? '' : 'none';
// Refresh the add button required label
const needsNow = req === 'always' || (req === 'on_yes' && val === 1) || (req === 'on_no' && val === 0);
const lbl = document.getElementById(`cl-img-label-${itemId}`);
if (lbl) { const span = lbl.querySelector('span:last-child'); if (span) span.textContent = (needsNow && !hasImages) ? 'Required' : 'Add'; }
}
}
function clAnswerText(itemId, val) {
if (!window._clInstanceAnswers) window._clInstanceAnswers = {};
if (!window._clInstanceAnswers[itemId]) window._clInstanceAnswers[itemId] = {};
window._clInstanceAnswers[itemId].answer_text = val;
const card = document.getElementById(`cl-q-${itemId}`);
if (card) card.style.outline = '';
}
function clAnswerNote(itemId, val) {
if (!window._clInstanceAnswers) window._clInstanceAnswers = {};
if (!window._clInstanceAnswers[itemId]) window._clInstanceAnswers[itemId] = {};
window._clInstanceAnswers[itemId].note = val;
}
async function clUploadImage(itemId, instanceId, input) {
const file = input.files[0]; if (!file) return;
// Reset input so the same file can be re-selected if needed
input.value = '';
const prog = document.getElementById(`cl-img-progress-${itemId}`);
const label = document.getElementById(`cl-img-label-${itemId}`);
if (prog) prog.style.display = 'flex';
if (label) label.style.display = 'none';
const fd = new FormData();
fd.append('image', file); fd.append('instance_id', instanceId); fd.append('item_id', itemId);
const res = await API.postForm('checklists/image_upload', fd);
if (prog) prog.style.display = 'none';
if (label) label.style.display = '';
if (res.success) {
if (!window._clInstanceAnswers[itemId]) window._clInstanceAnswers[itemId] = { images: [] };
if (!window._clInstanceAnswers[itemId].images) window._clInstanceAnswers[itemId].images = [];
window._clInstanceAnswers[itemId].images.push({ id: res.data.id, filename: res.data.filename });
// Re-render image grid
const card = document.getElementById(`cl-q-${itemId}`);
const req = card?.dataset.requiresImage;
const needsImage = req === 'always' || (req === 'on_yes' && window._clInstanceAnswers[itemId]?.answer_bool === 1) || (req === 'on_no' && window._clInstanceAnswers[itemId]?.answer_bool === 0);
const grid = document.getElementById(`cl-img-grid-${itemId}`);
if (grid) grid.outerHTML = _renderImageSection(itemId, window._clInstanceAnswers[itemId].images, false, needsImage);
// Update required label
const lbl = document.getElementById(`cl-img-label-${itemId}`);
if (lbl) { const span = lbl.querySelector('span:last-child'); if (span) span.textContent = 'Add'; }
Toast.show('Image uploaded.', 'success');
} else {
Toast.show(res.message, 'error');
}
}
async function clDeleteImage(itemId, imageId, filename) {
if (!window._clInstanceAnswers?.[itemId]) return;
const res = imageId
? await API.post('checklists/image_delete', { image_id: imageId })
: { success: true }; // legacy fallback with no DB id
if (res.success) {
window._clInstanceAnswers[itemId].images = (window._clInstanceAnswers[itemId].images || [])
.filter(img => img.filename !== filename);
const card = document.getElementById(`cl-q-${itemId}`);
const req = card?.dataset.requiresImage;
const needsImage = req === 'always' || (req === 'on_yes' && window._clInstanceAnswers[itemId]?.answer_bool === 1) || (req === 'on_no' && window._clInstanceAnswers[itemId]?.answer_bool === 0);
const grid = document.getElementById(`cl-img-grid-${itemId}`);
if (grid) grid.outerHTML = _renderImageSection(itemId, window._clInstanceAnswers[itemId].images, false, needsImage);
Toast.show('Image removed.', 'success');
} else Toast.show(res.message, 'error');
}
function _buildAnswerPayload() {
const answers = [];
const state = window._clInstanceAnswers || {};
for (const [itemId, ans] of Object.entries(state)) {
const card = document.getElementById(`cl-q-${itemId}`);
if (!card) continue;
const type = card.dataset.answerType;
answers.push({
item_id: parseInt(itemId),
answer_bool: type === 'yes_no' && ans.answer_bool !== null ? ans.answer_bool : null,
answer_text: (type === 'text' || type === 'number') ? (ans.answer_text || '') : null,
note: ans.note || null,
});
}
return answers;
}
async function clSaveProgress(instanceId) {
const btn = document.querySelector('.cl-submission-area .btn-outline');
if (btn) btn.classList.add('loading');
const notes = document.getElementById('cl-inst-notes')?.value || '';
const res = await API.post('checklists/instance_submit', { instance_id: instanceId, answers: _buildAnswerPayload(), notes, action: 'save' });
if (btn) btn.classList.remove('loading');
if (res.success) Toast.show('Progress saved.', 'success'); else Toast.show(res.message, 'error');
}
async function clSubmitChecklist(instanceId) {
const missing = [];
document.querySelectorAll('[data-item-id]').forEach(card => {
if (card.dataset.required !== '1') return;
const id = parseInt(card.dataset.itemId), type = card.dataset.answerType;
const ans = window._clInstanceAnswers?.[id] || {};
if (type === 'yes_no' && ans.answer_bool === null) missing.push(card);
if ((type === 'text' || type === 'number') && !ans.answer_text) missing.push(card);
if (type === 'photo_only' && (!ans.images || ans.images.length === 0)) missing.push(card);
if (card.dataset.requiresImage !== 'never' && (!ans.images || ans.images.length === 0) && needsImageForCard(card, ans)) missing.push(card);
});
if (missing.length) {
missing.forEach(c => c.style.outline = '2px solid var(--danger)');
Toast.show(`${missing.length} required question(s) not answered.`, 'error');
missing[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
return;
}
const confirmed = await Modal.confirm('Submit this checklist? You cannot edit it after submission.', 'Submit Checklist');
if (!confirmed) return;
const btn = document.querySelector('.cl-submission-area .btn-primary');
if (btn) btn.classList.add('loading');
const notes = document.getElementById('cl-inst-notes')?.value || '';
const res = await API.post('checklists/instance_submit', { instance_id: instanceId, answers: _buildAnswerPayload(), notes, action: 'submit' });
if (btn) btn.classList.remove('loading');
if (res.success) { Toast.show('Checklist submitted!', 'success'); renderInstanceFillOut(instanceId); }
else Toast.show(res.message, 'error');
}
// ── TEMPLATE DETAIL ──────────────────────────────────────────
async function openTemplateDetail(id) {
const res = await API.post('checklists/template_get', { id });
if (!res.success) { Toast.show(res.message, 'error'); return; }
const t = res.data.template, items = t.items || [];
const canManage = Auth.isDev() || Auth.isAdmin() || Auth.isHR();
const ansLabel = { yes_no: 'Yes/No', text: 'Text', number: 'Number', photo_only: 'Photo Only' };
const imgLabel = { never: 'No Image', always: 'Always', on_yes: 'If Yes', on_no: 'If No' };
Modal.open({
id: 'cl-detail', title: t.name, size: 'modal-lg',
body: `
Type
${clH(t.linked_type)}
Frequency
${Fmt.capitalize(t.frequency || '')}
${t.starts_at ? `
Starts
${Fmt.date(t.starts_at)}
` : ''}
${t.ends_at ? `
Ends
${Fmt.date(t.ends_at)}
` : ''}
${t.assigned_to_name ? `
Assigned To
${clH(t.assigned_to_name)}
` : ''}
${t.vehicle_reg ? `
Vehicle
🚗 ${clH(t.vehicle_reg)} ${clH(t.vehicle_make || '')} ${clH(t.vehicle_model || '')}
` : ''}
${t.description ? `${clH(t.description)}
` : ''}
${items.length} Question${items.length !== 1 ? 's' : ''}
${items.map((it, i) => `
${i + 1}${clH(it.item_text)}${it.is_required ? ' *' : ''}
${ansLabel[it.answer_type] || it.answer_type}
${it.requires_image !== 'never' ? `📷 ${imgLabel[it.requires_image] || it.requires_image}` : ''}
`).join('')}
`,
footer: `
${canManage ? `` : ''}
`,
});
}
// ── START INSTANCE MODAL ─────────────────────────────────────
async function openInstanceCreateModal(templateId, templateName, linkedType) {
const vehicles = _clVehicles || [], users = _clUsers || [];
Modal.open({
id: 'cl-start', title: `Start: ${templateName}`,
body: `
`,
footer: `
`,
});
}
async function submitCreateInstance(templateId) {
const btn = document.querySelector('#modal-cl-start .btn-primary');
if (btn) btn.classList.add('loading');
const data = getFormData('cl-start-form');
const res = await API.post('checklists/instance_create', { ...data, template_id: templateId });
if (btn) btn.classList.remove('loading');
if (res.success) { Toast.show('Checklist started!', 'success'); Modal.close(); Router.navigate('checklists', { instance_id: res.data.id }); }
else Toast.show(res.message, 'error');
}
// ── TEMPLATE BUILDER ─────────────────────────────────────────
let _builderItems = [];
let _builderEditId = 0;
let _bUid = 5000;
async function openTemplateBuilder(editId = 0) {
_builderItems = [];
_builderEditId = editId;
let template = null;
if (editId) {
const res = await API.post('checklists/template_get', { id: editId });
if (!res.success) { Toast.show(res.message, 'error'); return; }
template = res.data.template;
_builderItems = (template.items || []).map(it => ({
_uid: it.id, item_text: it.item_text,
answer_type: it.answer_type, requires_image: it.requires_image, is_required: it.is_required,
}));
}
const vehicles = _clVehicles || [], users = _clUsers || [], jobTypes = _clJobTypes || [];
const v = (k, def = '') => template ? (template[k] ?? def) : def;
Modal.open({
id: 'cl-builder',
title: editId ? `Edit: ${template.name}` : 'New Checklist Template',
size: 'modal-xl',
body: `
Questions ${_builderItems.length}
${_builderItems.length ? '' : '
No questions yet. Click “+ Add Question” to start.
'}
`,
footer: `
`,
});
_builderItems.forEach(it => _renderBuilderItem(it));
}
function clBuilderTypeChange(val) {
document.getElementById('cl-b-fleet-opts').style.display = val === 'fleet' ? '' : 'none';
}
function clBuilderAddItem() {
const item = { _uid: 'n' + (++_bUid), item_text: '', answer_type: 'yes_no', requires_image: 'never', is_required: 1 };
_builderItems.push(item);
const empty = document.getElementById('cl-bi-empty');
if (empty) empty.remove();
_renderBuilderItem(item);
_updateBuilderCount();
const list = document.getElementById('cl-builder-items');
if (list) setTimeout(() => { list.scrollTop = list.scrollHeight; const inp = document.getElementById(`cl-bi-${item._uid}`)?.querySelector('input[type=text]'); if (inp) inp.focus(); }, 60);
}
function _renderBuilderItem(item) {
const list = document.getElementById('cl-builder-items');
if (!list) return;
const div = document.createElement('div');
div.className = 'cl-builder-item'; div.id = `cl-bi-${item._uid}`;
div.innerHTML = `
⋮
`;
list.appendChild(div);
}
function clBuUpdate(uid, field, value) {
const item = _builderItems.find(i => String(i._uid) === String(uid));
if (item) item[field] = value;
}
function clBuilderRemoveItem(uid) {
_builderItems = _builderItems.filter(i => String(i._uid) !== String(uid));
const el = document.getElementById(`cl-bi-${uid}`);
if (el) el.remove();
_updateBuilderCount();
const list = document.getElementById('cl-builder-items');
if (list && !list.children.length) list.innerHTML = 'No questions yet. Click “+ Add Question” to start.
';
}
function _updateBuilderCount() {
const el = document.getElementById('cl-b-cnt'); if (el) el.textContent = _builderItems.length;
}
async function submitTemplateBuilder() {
const btn = document.querySelector('#modal-cl-builder .btn-primary');
if (btn) btn.classList.add('loading');
// Sync text inputs
document.querySelectorAll('.cl-builder-item').forEach((el, i) => {
const inp = el.querySelector('input[type=text]');
if (inp && _builderItems[i]) _builderItems[i].item_text = inp.value;
});
const data = getFormData('cl-builder-form');
const items = _builderItems.filter(it => (it.item_text || '').trim());
if (!data.name) { Toast.show('Checklist name is required.', 'error'); if (btn) btn.classList.remove('loading'); return; }
const res = await API.post('checklists/template_save', { ...data, id: _builderEditId || 0, is_active: data.is_active ? 1 : 0, items });
if (btn) btn.classList.remove('loading');
if (res.success) {
Toast.show(_builderEditId ? 'Template updated.' : 'Template created.', 'success');
Modal.close(); _clJobTypes = null; await _loadClCaches(); renderChecklistsTab('templates');
} else Toast.show(res.message, 'error');
}
async function deleteTemplate(id, name) {
if (!await Modal.confirm(`Delete checklist "${name}"?`, 'Delete Template', true)) return;
const res = await API.post('checklists/template_delete', { id });
if (res.success) { Toast.show(res.message, 'success'); renderChecklistsTab('templates'); }
else Toast.show(res.message, 'error');
}
// ── Utility ───────────────────────────────────────────────────
function needsImageForCard(card, ans) {
const req = card.dataset.requiresImage;
if (req === 'always') return true;
if (req === 'on_yes' && ans.answer_bool === 1) return true;
if (req === 'on_no' && ans.answer_bool === 0) return true;
return false;
}
function clH(s) { return String(s ?? '').replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); }
// ── CSS for this module ───────────────────────────────────────
(function injectChecklistStyles() {
if (document.getElementById('cl-styles')) return;
const style = document.createElement('style');
style.id = 'cl-styles';
style.textContent = `
.cl-question-card { background:var(--surface); border:1px solid var(--border); border-radius:var(--radius); padding:1rem 1.25rem; transition:outline .15s; }
.cl-q-header { display:flex; align-items:flex-start; gap:.75rem; }
.cl-q-num { width:26px; min-width:26px; height:26px; background:var(--primary); color:#fff; border-radius:50%; display:flex; align-items:center; justify-content:center; font-size:.75rem; font-weight:700; flex-shrink:0; }
.cl-q-text { font-weight:500; line-height:1.5; }
.cl-yn-btn { border:2px solid var(--border); background:transparent; padding:.4rem 1.25rem; border-radius:var(--radius); font-weight:600; cursor:pointer; transition:all .15s; }
.cl-yn-btn:hover { border-color:var(--primary); }
.cl-yn-btn.active-yes { background:#dcfce7; border-color:#16a34a; color:#15803d; }
.cl-yn-btn.active-no { background:#fee2e2; border-color:#dc2626; color:#b91c1c; }
.cl-img-grid { display:flex; flex-wrap:wrap; gap:.5rem; align-items:flex-start; margin-top:.5rem; }
.cl-img-thumb { position:relative; flex-shrink:0; }
.cl-img-thumb-del { position:absolute; top:-6px; right:-6px; width:18px; height:18px; border-radius:50%; background:var(--danger); color:#fff; border:none; cursor:pointer; font-size:.6rem; display:flex; align-items:center; justify-content:center; line-height:1; padding:0; }
.cl-img-thumb-del:hover { background:#b91c1c; }
.cl-img-add-btn { width:80px; height:80px; display:inline-flex; flex-direction:column; align-items:center; justify-content:center; gap:2px; border:2px dashed var(--border); border-radius:6px; cursor:pointer; font-size:.85rem; color:var(--text-muted); background:var(--bg); transition:border-color .15s; flex-shrink:0; }
.cl-img-add-btn:hover { border-color:var(--primary); color:var(--primary); }
.cl-submission-area { background:var(--surface); border:1px solid var(--border); border-radius:var(--radius); padding:1.25rem; }
.cl-readonly-text { background:var(--bg); border:1px solid var(--border); border-radius:var(--radius); padding:.5rem .75rem; font-size:.9rem; white-space:pre-wrap; }
.cl-note-readonly { background:rgba(0,0,0,.03); border-radius:var(--radius); padding:.4rem .75rem; font-size:.85rem; color:var(--text-muted); }
.cl-builder-grid { display:grid; grid-template-columns:1fr 1fr; gap:1.5rem; }
@media(max-width:720px) { .cl-builder-grid { grid-template-columns:1fr; } }
.cl-builder-section-title { font-family:var(--font-display); font-size:.8rem; text-transform:uppercase; letter-spacing:.06em; color:var(--text-muted); margin-bottom:.75rem; }
.cl-builder-item { display:flex; align-items:flex-start; gap:.5rem; background:var(--surface); border:1px solid var(--border); border-radius:var(--radius); padding:.6rem; }
.cl-bi-drag { cursor:grab; color:var(--text-muted); padding:.15rem .1rem; font-size:1.1rem; margin-top:.15rem; }
.cl-bi-body { flex:1; display:flex; flex-direction:column; gap:.4rem; }
.cl-bi-opts { display:flex; align-items:center; gap:.4rem; flex-wrap:wrap; }
.cl-bi-opts select { flex:1; min-width:90px; }
.cl-bi-del { background:none; border:none; cursor:pointer; color:var(--text-muted); padding:.2rem; border-radius:4px; display:flex; align-items:center; }
.cl-bi-del:hover { color:var(--danger); background:var(--danger-light,#fee2e2); }
.badge-lg { font-size:.9rem; padding:.35em .9em; }
`;
document.head.appendChild(style);
})();