Add tooltip for shift constraints and enhance weekday checkbox handling in worker form
This commit is contained in:
@@ -5,6 +5,16 @@
|
|||||||
{% for s in rota.shifts %}
|
{% for s in rota.shifts %}
|
||||||
<li>
|
<li>
|
||||||
<strong>{{ s.name }}</strong> — sites: {{ s.sites|join:", " }} — days: {{ s.days|join:", " }} — workers: {{ s.workers_required }}
|
<strong>{{ s.name }}</strong> — sites: {{ s.sites|join:", " }} — days: {{ s.days|join:", " }} — workers: {{ s.workers_required }}
|
||||||
|
{% if s.constraints %}
|
||||||
|
<div style="margin-top:0.25rem;">
|
||||||
|
<span class="tags">
|
||||||
|
{% for c in s.constraints %}
|
||||||
|
{# Build a simple tooltip showing key: value pairs for the constraint on hover #}
|
||||||
|
<span class="tag is-light" title="{% for k,v in c.items %}{{ k }}: {{ v }}{% if not forloop.last %}; {% endif %}{% endfor %}">{{ c.name }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% if s.balance_offset is defined and s.balance_offset %}
|
{% if s.balance_offset is defined and s.balance_offset %}
|
||||||
— balance offset: {{ s.balance_offset }}
|
— balance offset: {{ s.balance_offset }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -268,6 +268,7 @@
|
|||||||
if(textarea) textarea.style.display='none';
|
if(textarea) textarea.style.display='none';
|
||||||
const listEl = document.getElementById(cfg.container);
|
const listEl = document.getElementById(cfg.container);
|
||||||
const addBtn = document.getElementById(cfg.addButton);
|
const addBtn = document.getElementById(cfg.addButton);
|
||||||
|
const WEEKDAYS = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'];
|
||||||
|
|
||||||
function parse(){
|
function parse(){
|
||||||
let v = textarea ? textarea.value : ''; if(!v) return [];
|
let v = textarea ? textarea.value : ''; if(!v) return [];
|
||||||
@@ -284,6 +285,35 @@
|
|||||||
const field = document.createElement('div'); field.className='field';
|
const field = document.createElement('div'); field.className='field';
|
||||||
const label = document.createElement('label'); label.className='label'; label.textContent=f.label; field.appendChild(label);
|
const label = document.createElement('label'); label.className='label'; label.textContent=f.label; field.appendChild(label);
|
||||||
const control = document.createElement('div'); control.className='control';
|
const control = document.createElement('div'); control.className='control';
|
||||||
|
|
||||||
|
// Special-case: render weekday checkboxes when the field name is 'days'
|
||||||
|
if(f.name === 'days'){
|
||||||
|
const box = document.createElement('div'); box.className='buttons'; box.setAttribute('data-field', f.name);
|
||||||
|
WEEKDAYS.forEach(d => {
|
||||||
|
const btn = document.createElement('label'); btn.className='button is-small is-light'; btn.style.marginRight='0.25rem';
|
||||||
|
const chk = document.createElement('input'); chk.type='checkbox'; chk.value = d; chk.style.marginRight='0.35rem';
|
||||||
|
// mark checked if item[f.name] contains this day (accept set/list/string)
|
||||||
|
try{
|
||||||
|
const cur = item[f.name];
|
||||||
|
if(cur){
|
||||||
|
if(Array.isArray(cur)){
|
||||||
|
if(cur.indexOf(d)!==-1) chk.checked = true;
|
||||||
|
} else if(typeof cur === 'string'){
|
||||||
|
const parts = cur.split(',').map(s=>s.trim()); if(parts.indexOf(d)!==-1) chk.checked = true;
|
||||||
|
} else if(typeof cur === 'object'){
|
||||||
|
// set-like
|
||||||
|
if(cur.hasOwnProperty(d) || (Array.from(Object.values(cur)).indexOf(d)!==-1)) chk.checked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}catch(e){}
|
||||||
|
chk.onchange = ()=>{ sync(idx); };
|
||||||
|
btn.appendChild(chk);
|
||||||
|
const span = document.createElement('span'); span.textContent = d; btn.appendChild(span);
|
||||||
|
box.appendChild(btn);
|
||||||
|
});
|
||||||
|
control.appendChild(box);
|
||||||
|
field.appendChild(control); col.appendChild(field); row.appendChild(col);
|
||||||
|
} else {
|
||||||
let input;
|
let input;
|
||||||
if(f.type==='date'){
|
if(f.type==='date'){
|
||||||
input = document.createElement('input'); input.type='date'; input.className='datepicker input'; input.setAttribute('autocomplete','off'); input.value = item[f.name] || '';
|
input = document.createElement('input'); input.type='date'; input.className='datepicker input'; input.setAttribute('autocomplete','off'); input.value = item[f.name] || '';
|
||||||
@@ -292,8 +322,11 @@
|
|||||||
} else {
|
} else {
|
||||||
input = document.createElement('input'); input.type='text'; input.className='input'; input.value = item[f.name] || '';
|
input = document.createElement('input'); input.type='text'; input.className='input'; input.value = item[f.name] || '';
|
||||||
}
|
}
|
||||||
|
// tag this control with the field name so sync() can find it reliably
|
||||||
|
input.setAttribute('data-field', f.name);
|
||||||
input.onchange = ()=>{ sync(idx); };
|
input.onchange = ()=>{ sync(idx); };
|
||||||
control.appendChild(input); field.appendChild(control); col.appendChild(field); row.appendChild(col);
|
control.appendChild(input); field.appendChild(control); col.appendChild(field); row.appendChild(col);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
// remove
|
// remove
|
||||||
const colRm = document.createElement('div'); colRm.className='column is-narrow'; const fr = document.createElement('div'); fr.className='field'; const ctr = document.createElement('div'); ctr.className='control'; const rm = document.createElement('button'); rm.type='button'; rm.className='button is-danger is-light is-small'; rm.textContent='Remove'; rm.onclick = ()=>{ removeAt(idx); }; ctr.appendChild(rm); fr.appendChild(ctr); colRm.appendChild(fr); row.appendChild(colRm);
|
const colRm = document.createElement('div'); colRm.className='column is-narrow'; const fr = document.createElement('div'); fr.className='field'; const ctr = document.createElement('div'); ctr.className='control'; const rm = document.createElement('button'); rm.type='button'; rm.className='button is-danger is-light is-small'; rm.textContent='Remove'; rm.onclick = ()=>{ removeAt(idx); }; ctr.appendChild(rm); fr.appendChild(ctr); colRm.appendChild(fr); row.appendChild(colRm);
|
||||||
@@ -302,7 +335,38 @@
|
|||||||
return wrap;
|
return wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sync(i){ const data = parse(); const el = listEl.children[i]; if(!el) return; const inputs = el.querySelectorAll('input'); const obj = {}; cfg.fields.forEach((f,j)=>{ const val = inputs[j].value; if(val!=='') obj[f.name]= f.type==='int'?parseInt(val,10):val; }); data[i]=obj; if(textarea) textarea.value = JSON.stringify(data); }
|
function sync(i){
|
||||||
|
const data = parse();
|
||||||
|
const el = listEl.children[i]; if(!el) return;
|
||||||
|
const obj = {};
|
||||||
|
cfg.fields.forEach((f)=>{
|
||||||
|
const fld = el.querySelector('[data-field="'+f.name+'"]');
|
||||||
|
if(!fld){
|
||||||
|
// fallback: try to find an input by label text
|
||||||
|
const inp = el.querySelector('input');
|
||||||
|
if(inp && inp.value!=='') obj[f.name] = f.type==='int'?parseInt(inp.value,10):inp.value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// if this is a checkbox group (for days), fld will be a container
|
||||||
|
if(f.name === 'days'){
|
||||||
|
const checks = fld.querySelectorAll('input[type=checkbox]');
|
||||||
|
const vals = Array.from(checks).filter(c=>c.checked).map(c=>c.value);
|
||||||
|
if(vals.length) obj[f.name] = vals;
|
||||||
|
} else {
|
||||||
|
// single input control
|
||||||
|
if(fld.tagName === 'INPUT'){
|
||||||
|
const v = fld.value;
|
||||||
|
if(v !== '') obj[f.name] = f.type==='int'?parseInt(v,10):v;
|
||||||
|
} else {
|
||||||
|
// container wrapping an input
|
||||||
|
const inp = fld.querySelector('input');
|
||||||
|
if(inp && inp.value !== '') obj[f.name] = f.type==='int'?parseInt(inp.value,10):inp.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
data[i]=obj;
|
||||||
|
if(textarea) textarea.value = JSON.stringify(data);
|
||||||
|
}
|
||||||
|
|
||||||
function removeAt(i){ const data = parse(); data.splice(i,1); if(textarea) textarea.value = JSON.stringify(data); render(); }
|
function removeAt(i){ const data = parse(); data.splice(i,1); if(textarea) textarea.value = JSON.stringify(data); render(); }
|
||||||
|
|
||||||
@@ -321,8 +385,8 @@
|
|||||||
makeSimpleListEditor({textarea:'id_allowed_multi_shift_sets', container:'ams-list', addButton:'add-ams', fields:[{name:'shifts',label:'Shifts (comma-separated)',type:'text'}]});
|
makeSimpleListEditor({textarea:'id_allowed_multi_shift_sets', container:'ams-list', addButton:'add-ams', fields:[{name:'shifts',label:'Shifts (comma-separated)',type:'text'}]});
|
||||||
|
|
||||||
// Hard day deps/exclusions need days/weeks/start/end
|
// Hard day deps/exclusions need days/weeks/start/end
|
||||||
makeSimpleListEditor({textarea:'id_hard_day_dependencies', container:'hdd-list', addButton:'add-hdd', fields:[{name:'days',label:'Days (comma-separated)',type:'text'},{name:'weeks',label:'Weeks (comma-separated ints)',type:'text'},{name:'start_date',label:'Start date',type:'date'},{name:'end_date',label:'End date',type:'date'}]});
|
makeSimpleListEditor({textarea:'id_hard_day_dependencies', container:'hdd-list', addButton:'add-hdd', fields:[{name:'days',label:'Days (weekdays)',type:'text'},{name:'weeks',label:'Weeks (comma-separated ints)',type:'text'},{name:'start_date',label:'Start date',type:'date'},{name:'end_date',label:'End date',type:'date'}]});
|
||||||
makeSimpleListEditor({textarea:'id_hard_day_exclusions', container:'hde-list', addButton:'add-hde', fields:[{name:'days',label:'Days (comma-separated)',type:'text'},{name:'weeks',label:'Weeks (comma-separated ints)',type:'text'},{name:'start_date',label:'Start date',type:'date'},{name:'end_date',label:'End date',type:'date'}]});
|
makeSimpleListEditor({textarea:'id_hard_day_exclusions', container:'hde-list', addButton:'add-hde', fields:[{name:'days',label:'Days (weekdays)',type:'text'},{name:'weeks',label:'Weeks (comma-separated ints)',type:'text'},{name:'start_date',label:'Start date',type:'date'},{name:'end_date',label:'End date',type:'date'}]});
|
||||||
|
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user