// ============================================================ // Job Card Settings Module // ============================================================ let _jcsTypes = null; // cached types async function renderJobCardSettings(params = {}) { const isAdmin = Auth.isAdmin() || Auth.isDev(); if (!isAdmin) { Router.navigate('jobcards'); return; } const content = document.getElementById('page-content'); content.innerHTML = `

Job Card Settings

Configure job types and their default planning tasks & checklist items

`; await jcsLoadTypes(); } async function jcsLoadTypes() { const body = document.getElementById('jcs-body'); if (!body) return; const res = await API.post('jobcard_types/list', {}); if (!res.success) { body.innerHTML = `

${res.message}

`; return; } _jcsTypes = res.data.types || []; if (!_jcsTypes.length) { body.innerHTML = `
๐Ÿ—‚๏ธ
No job types yet

Create job types to auto-fill planning tasks and checklist items when a new job card is created.

`; return; } body.innerHTML = `
${_jcsTypes.map(t => jcsTypeCard(t)).join('')}
+
New Job Type
`; } function jcsTypeCard(t) { const planItems = t.default_planning_items || []; const checkItems = t.default_checklist_items || []; const ICONS = { task: '๐Ÿ“‹', tool: '๐Ÿ”ง', part: '๐Ÿ“ฆ', note: '๐Ÿ“', safety: 'โš ๏ธ' }; return `
${jcsH(t.name)}
${jcsH(t.slug)} ${t.is_active ? '' : ' ยท Inactive'}
${t.description ? `

${jcsH(t.description)}

` : ''}
๐Ÿ“‹ Planning Tasks
${planItems.length ? `
    ${planItems.slice(0, 5).map(it => `
  • ${ICONS[it.item_type] || '๐Ÿ“‹'} ${jcsH(it.item)}
  • `).join('')} ${planItems.length > 5 ? `
  • +${planItems.length - 5} more
  • ` : ''}
` : `None set`}
โ˜‘๏ธ Checklist Items
${checkItems.length ? `
    ${checkItems.slice(0, 5).map(it => `
  • โ˜ ${jcsH(it.item)}
  • `).join('')} ${checkItems.length > 5 ? `
  • +${checkItems.length - 5} more
  • ` : ''}
` : `None set`}
`; } // โ”€โ”€ In-memory state for item editors โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ let _jcsPlanItems = []; let _jcsCheckItems = []; const JCS_PLAN_ICONS = { task: '๐Ÿ“‹', tool: '๐Ÿ”ง', part: '๐Ÿ“ฆ', note: '๐Ÿ“', safety: 'โš ๏ธ' }; async function jcsOpenTypeModal(editId = 0) { let type = null; if (editId) type = (_jcsTypes || []).find(t => t.id === editId); const v = (k, def = '') => type ? (type[k] ?? def) : def; _jcsPlanItems = JSON.parse(JSON.stringify(v('default_planning_items', []))); _jcsCheckItems = JSON.parse(JSON.stringify(v('default_checklist_items', []))); Modal.open({ id: 'jcs-type', title: editId ? `Edit: ${type?.name}` : 'New Job Type', size: 'modal-xl', body: `
Job Type Details
${editId ? `` : ''}
๐Ÿ“‹ Planning Tasks

Auto-fill the Planning tab when this job type is selected on a new job card.

${!_jcsPlanItems.length ? '

No tasks yet

' : ''}
โ˜‘๏ธ Checklist Items

Auto-fill the Checklist tab when this job type is selected on a new job card.

${!_jcsCheckItems.length ? '

No items yet

' : ''}
`, footer: ` ` }); _jcsPlanItems.forEach((it, i) => _jcsRenderPlanItem(it, i)); _jcsCheckItems.forEach((it, i) => _jcsRenderCheckItem(it, i)); } // โ”€โ”€ Planning item helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ function jcsAddPlanItem() { const empty = document.getElementById('jcs-plan-empty'); if (empty) empty.remove(); const item = { item: '', item_type: 'task' }; _jcsPlanItems.push(item); _jcsRenderPlanItem(item, _jcsPlanItems.length - 1); } function _jcsRenderPlanItem(item, idx) { const list = document.getElementById('jcs-plan-items'); if (!list) return; const div = document.createElement('div'); div.id = `jcs-pi-${idx}`; div.style.cssText = 'display:flex;gap:.4rem;align-items:center;background:var(--surface);border:1px solid var(--border);border-radius:6px;padding:.4rem .5rem'; div.innerHTML = ` `; list.appendChild(div); if (!item.item) div.querySelector('input')?.focus(); } function _jcsRemovePlanItem(idx) { _jcsPlanItems.splice(idx, 1); const list = document.getElementById('jcs-plan-items'); if (list) { list.innerHTML = ''; _jcsPlanItems.forEach((it, i) => _jcsRenderPlanItem(it, i)); } if (!_jcsPlanItems.length) { document.getElementById('jcs-plan-items').innerHTML = '

No tasks yet

'; } } // โ”€โ”€ Checklist item helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ function jcsAddCheckItem() { const empty = document.getElementById('jcs-check-empty'); if (empty) empty.remove(); const item = { item: '' }; _jcsCheckItems.push(item); _jcsRenderCheckItem(item, _jcsCheckItems.length - 1); } function _jcsRenderCheckItem(item, idx) { const list = document.getElementById('jcs-check-items'); if (!list) return; const div = document.createElement('div'); div.id = `jcs-ci-${idx}`; div.style.cssText = 'display:flex;gap:.4rem;align-items:center;background:var(--surface);border:1px solid var(--border);border-radius:6px;padding:.4rem .5rem'; div.innerHTML = ` โ˜ `; list.appendChild(div); if (!item.item) div.querySelector('input')?.focus(); } function _jcsRemoveCheckItem(idx) { _jcsCheckItems.splice(idx, 1); const list = document.getElementById('jcs-check-items'); if (list) { list.innerHTML = ''; _jcsCheckItems.forEach((it, i) => _jcsRenderCheckItem(it, i)); } if (!_jcsCheckItems.length) { document.getElementById('jcs-check-items').innerHTML = '

No items yet

'; } } // โ”€โ”€ Save โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ async function jcsSubmitType() { const btn = document.querySelector('#modal-jcs-type .btn-primary'); if (btn) btn.classList.add('loading'); const data = getFormData('jcs-type-form'); data.is_active = data.is_active ? 1 : 0; // Flush any typed values document.querySelectorAll('#jcs-plan-items input:not([type=hidden])').forEach((inp, i) => { if (_jcsPlanItems[i]) _jcsPlanItems[i].item = inp.value; }); document.querySelectorAll('#jcs-check-items input:not([type=hidden])').forEach((inp, i) => { if (_jcsCheckItems[i]) _jcsCheckItems[i].item = inp.value; }); data.default_planning_items = JSON.stringify(_jcsPlanItems.filter(it => it.item?.trim())); data.default_checklist_items = JSON.stringify(_jcsCheckItems.filter(it => it.item?.trim())); const res = await API.post('jobcard_types/save', data); if (btn) btn.classList.remove('loading'); if (res.success) { Toast.show(res.message, 'success'); Modal.close(); _jcsTypes = null; _jcTypes = null; // also clear jobcards.js cache await jcsLoadTypes(); } else { Toast.show(res.message, 'error'); } } async function jcsDeleteType(id, btnEl) { const name = typeof btnEl === 'string' ? btnEl : (btnEl?.dataset?.name || 'this type'); if (!await Modal.confirm(`Delete job type "${jcsH(name)}"?
This cannot be undone.`, 'Delete Job Type', true)) return; const res = await API.post('jobcard_types/delete', { id }); if (res.success) { Toast.show(res.message, 'success'); _jcsTypes = null; _jcTypes = null; await jcsLoadTypes(); } else { Toast.show(res.message, 'error'); } } // โ”€โ”€ Utility โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ function jcsH(s) { return String(s ?? '').replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); }