// ============================================================
// 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 = `
`;
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('')}
`;
}
function jcsTypeCard(t) {
const planItems = t.default_planning_items || [];
const checkItems = t.default_checklist_items || [];
const ICONS = { task: '๐', tool: '๐ง', part: '๐ฆ', note: '๐', safety: 'โ ๏ธ' };
return `
${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: `
๐ 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, '"');
}