Enhance worker creation modal to include rota-specific choices and shift options, improving user experience and data handling.

This commit is contained in:
Ross
2025-12-19 22:03:34 +00:00
parent cf9e9ee854
commit 0f9cd52911
2 changed files with 72 additions and 18 deletions
@@ -378,17 +378,39 @@
control.appendChild(box);
field.appendChild(control); col.appendChild(field); row.appendChild(col);
} else {
let input;
if(f.type==='date'){
input = document.createElement('input'); input.type='date'; input.className='datepicker input'; input.setAttribute('autocomplete','off'); input.value = item[f.name] || '';
} else if(f.type==='int'){
input = document.createElement('input'); input.type='number'; input.className='input'; input.value = item[f.name] || '';
} else {
input = document.createElement('input'); input.type='text'; input.className='input'; input.value = item[f.name] || '';
}
let input;
if(f.type==='date'){
input = document.createElement('input'); input.type='date'; input.className='datepicker input'; input.setAttribute('autocomplete','off'); input.value = item[f.name] || '';
} else if(f.type==='int'){
input = document.createElement('input'); input.type='number'; input.className='input'; input.value = item[f.name] || '';
} else if(f.type==='shift'){
// render a select populated with shift options passed via cfg
input = document.createElement('select'); input.className='select';
// wrap select in a div.select to match Bulma styling
const selWrap = document.createElement('div'); selWrap.className = 'select';
const sel = document.createElement('select');
const emptyOpt = document.createElement('option'); emptyOpt.value = ''; emptyOpt.text = '(any)'; sel.appendChild(emptyOpt);
const opts = (cfg && cfg.shiftOptions) ? cfg.shiftOptions : (typeof _shiftOptions !== 'undefined' ? _shiftOptions : []);
if(Array.isArray(opts)){
opts.forEach(o=>{ const op = document.createElement('option'); op.value = o; op.text = o; if(item[f.name] === o) op.selected = true; sel.appendChild(op); });
}
sel.value = item[f.name] || '';
sel.onchange = ()=>{ sync(idx); };
sel.setAttribute('data-field', f.name);
selWrap.appendChild(sel);
// set 'input' variable to the wrapper so downstream code appends it
input = selWrap;
} else {
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); };
if(input.tagName === 'DIV' && input.querySelector('select')){
// wrapper created for select; the inner select already has data-field
// nothing to do here
} else {
input.setAttribute('data-field', f.name);
input.onchange = ()=>{ sync(idx); };
}
control.appendChild(input); field.appendChild(control); col.appendChild(field); row.appendChild(col);
}
});
@@ -418,12 +440,12 @@
if(vals.length) obj[f.name] = vals;
} else {
// single input control
if(fld.tagName === 'INPUT'){
if(fld.tagName === 'INPUT' || fld.tagName === 'SELECT'){
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');
// container wrapping an input (e.g. div.select wrapper or custom control)
const inp = fld.querySelector('input') || fld.querySelector('select');
if(inp && inp.value !== '') obj[f.name] = f.type==='int'?parseInt(inp.value,10):inp.value;
}
}
@@ -442,10 +464,16 @@
makeSimpleListEditor({textarea:'id_oop', container:'oop-list', addButton:'add-oop', fields:[{name:'start_date',label:'Start date',type:'date'},{name:'end_date',label:'End date',type:'date'},{name:'reason',label:'Reason',type:'text'}]});
makeSimpleListEditor({textarea:'id_not_available_to_work', container:'nav-list', addButton:'add-nav', fields:[{name:'date',label:'Date',type:'date'},{name:'reason',label:'Reason',type:'text'}]});
makeSimpleListEditor({textarea:'id_pref_not_to_work', container:'pref-list', addButton:'add-pref', fields:[{name:'date',label:'Date',type:'date'},{name:'reason',label:'Reason',type:'text'}]});
makeSimpleListEditor({textarea:'id_work_requests', container:'wr-list', addButton:'add-wr', fields:[{name:'date',label:'Date',type:'date'},{name:'shift',label:'Shift',type:'text'}]});
makeSimpleListEditor({textarea:'id_locum_availability', container:'loc-list', addButton:'add-loc', fields:[{name:'date',label:'Date',type:'date'},{name:'shift',label:'Shift',type:'text'}]});
makeSimpleListEditor({textarea:'id_forced_assignments', container:'fa-list', addButton:'add-fa', fields:[{name:'week',label:'Week',type:'int'},{name:'day',label:'Day',type:'text'},{name:'shift',label:'Shift',type:'text'}]});
makeSimpleListEditor({textarea:'id_forced_assignments_by_date', container:'fabd-list', addButton:'add-fabd', fields:[{name:'date',label:'Date',type:'date'},{name:'shift',label:'Shift',type:'text'}]});
// If the server provided shift names for the current rota, use them
var _shiftOptions = [];
try{
_shiftOptions = {{ shift_names_json|default:'[]'|safe }};
}catch(e){ _shiftOptions = []; }
makeSimpleListEditor({textarea:'id_work_requests', container:'wr-list', addButton:'add-wr', fields:[{name:'date',label:'Date',type:'date'},{name:'shift',label:'Shift',type:'shift'}], shiftOptions:_shiftOptions});
makeSimpleListEditor({textarea:'id_locum_availability', container:'loc-list', addButton:'add-loc', fields:[{name:'date',label:'Date',type:'date'},{name:'shift',label:'Shift',type:'shift'}], shiftOptions:_shiftOptions});
makeSimpleListEditor({textarea:'id_forced_assignments', container:'fa-list', addButton:'add-fa', fields:[{name:'week',label:'Week',type:'int'},{name:'day',label:'Day',type:'text'},{name:'shift',label:'Shift',type:'shift'}], shiftOptions:_shiftOptions});
makeSimpleListEditor({textarea:'id_forced_assignments_by_date', container:'fabd-list', addButton:'add-fabd', fields:[{name:'date',label:'Date',type:'date'},{name:'shift',label:'Shift',type:'shift'}], shiftOptions:_shiftOptions});
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
+27 -1
View File
@@ -373,9 +373,35 @@ def worker_create(request):
# HTMX GET: return modal partial
if is_hx:
# If a rota_id query param is provided, include the rota in the
# modal context so the template can offer rota-specific choices
rota = None
rota_id = request.GET.get("rota_id") or request.POST.get("rota_id")
if rota_id:
try:
rota = get_object_or_404(RotaSchedule, pk=int(rota_id))
except Exception:
rota = None
# Prepare a JSON-encoded list of shift names for client-side use
shift_names_json = '[]'
try:
import json as _json
if rota is not None and getattr(rota, 'shifts', None):
# rota.shifts is stored as list of dicts
names = []
for s in (rota.shifts or []):
try:
names.append(s.get('name'))
except Exception:
continue
shift_names_json = _json.dumps(names)
except Exception:
shift_names_json = '[]'
modal_html = render_to_string(
"rota/partials/worker_form_modal.html",
{"form": form, "form_action": request.get_full_path()},
{"form": form, "form_action": request.get_full_path(), "rota": rota, "shift_names_json": shift_names_json},
request=request,
)
return HttpResponse(modal_html)