diff --git a/djangorota/djangorota/templates/rota/partials/worker_form_modal.html b/djangorota/djangorota/templates/rota/partials/worker_form_modal.html index abd0496..082d6dd 100644 --- a/djangorota/djangorota/templates/rota/partials/worker_form_modal.html +++ b/djangorota/djangorota/templates/rota/partials/worker_form_modal.html @@ -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 diff --git a/djangorota/rota/views.py b/djangorota/rota/views.py index d91c5c2..ffa137f 100644 --- a/djangorota/rota/views.py +++ b/djangorota/rota/views.py @@ -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)