From bd5ca8f64cbf19f32558645c7e47b3731a831903 Mon Sep 17 00:00:00 2001 From: Ross Date: Sat, 13 Dec 2025 21:33:39 +0000 Subject: [PATCH] . --- .../rota/partials/shift_form_modal.html | 55 +++++++++++++++++-- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/djangorota/djangorota/templates/rota/partials/shift_form_modal.html b/djangorota/djangorota/templates/rota/partials/shift_form_modal.html index e71aaaf..13a2a32 100644 --- a/djangorota/djangorota/templates/rota/partials/shift_form_modal.html +++ b/djangorota/djangorota/templates/rota/partials/shift_form_modal.html @@ -33,8 +33,33 @@ function _close_shift_modal_targets(){ // Constraint type schemas: name -> fields metadata const SCHEMAS = { "night": {label: "Night constraint", fields: [ {name: "options", label: "Options (free JSON)", type: "json"} ]}, - "pre": {label: "Pre-shift", fields: [ {name: "days", label: "Days (comma-separated)", type: "list"}, {name: "ignore_shifts", label: "Ignore shifts (comma-separated)", type: "list"} ]}, - "post": {label: "Post-shift", fields: [ {name: "days", label: "Days (comma-separated)", type: "list"}, {name: "ignore_shifts", label: "Ignore shifts (comma-separated)", type: "list"} ]}, + // PreShiftConstraint fields from rota_generator/shifts.py: + // weeks: Optional[List[int]] + // start_date: Optional[date] + // end_date: Optional[date] + // days: Optional[int] + // ignore_shifts: List[str] + // exclude_days: List[str] + // allow_self: bool + "pre": {label: "Pre-shift", fields: [ + {name: "weeks", label: "Weeks (comma-separated integers)", type: "list_int"}, + {name: "start_date", label: "Start date", type: "date"}, + {name: "end_date", label: "End date", type: "date"}, + {name: "days", label: "Days (integer)", type: "int"}, + {name: "ignore_shifts", label: "Ignore shifts (comma-separated)", type: "list"}, + {name: "exclude_days", label: "Exclude days (comma-separated)", type: "list"}, + {name: "allow_self", label: "Allow self", type: "bool"} + ]}, + // PostShiftConstraint same fields + "post": {label: "Post-shift", fields: [ + {name: "weeks", label: "Weeks (comma-separated integers)", type: "list_int"}, + {name: "start_date", label: "Start date", type: "date"}, + {name: "end_date", label: "End date", type: "date"}, + {name: "days", label: "Days (integer)", type: "int"}, + {name: "ignore_shifts", label: "Ignore shifts (comma-separated)", type: "list"}, + {name: "exclude_days", label: "Exclude days (comma-separated)", type: "list"}, + {name: "allow_self", label: "Allow self", type: "bool"} + ]}, "min_summed_grade_by_shifts_per_day": {label: "Min summed grade by shifts per day", fields: [ {name: "shifts", label: "Shifts (comma-separated)", type: "list"}, {name: "min_grade_sum", label: "Minimum grade sum", type: "int"}, {name: "days", label: "Days (comma-separated)", type: "list"} ]}, "limit_grade_number": {label: "Limit grade number", fields: [ {name: "grade", label: "Grade", type: "int"}, {name: "max_number", label: "Max number", type: "int"} ]}, "minimum_grade_number": {label: "Minimum grade number", fields: [ {name: "grade", label: "Grade", type: "int"}, {name: "min_number", label: "Min number", type: "int"} ]}, @@ -105,16 +130,32 @@ function _close_shift_modal_targets(){ fieldWrap.appendChild(label); const control = document.createElement('div'); control.className='control'; let input; - if(f.type==='text' || f.type==='int' || f.type==='float' || f.type==='list'){ + if(f.type==='text' || f.type==='int' || f.type==='float' || f.type==='list' || f.type==='list_int'){ input = document.createElement('input'); input.className='input'; input.type='text'; const cur = (c.options && c.options[f.name]!==undefined) ? c.options[f.name] : (c[f.name]!==undefined?c[f.name]:''); - if(Array.isArray(cur) && f.type==='list') input.value = cur.join(','); else input.value = cur===null? '': String(cur); + if(Array.isArray(cur) && (f.type==='list' || f.type==='list_int')) input.value = cur.join(','); else input.value = cur===null? '': String(cur); input.oninput = syncToData; } else if(f.type==='json'){ input = document.createElement('textarea'); input.className='textarea'; input.rows=3; const cur = (c.options && c.options[f.name]!==undefined) ? c.options[f.name] : (c[f.name]!==undefined?c[f.name]:''); try{ input.value = JSON.stringify(cur); }catch(e){ input.value = String(cur); } input.oninput = syncToData; + } else if(f.type==='bool'){ + // checkbox for boolean + const cb = document.createElement('input'); cb.type='checkbox'; cb.className='checkbox'; + const curb = (c.options && c.options[f.name]!==undefined) ? c.options[f.name] : (c[f.name]!==undefined?c[f.name]:false); + cb.checked = !!curb; + cb.setAttribute('data-field-name', f.name); + cb.onchange = syncToData; + control.appendChild(cb); + fieldWrap.appendChild(control); + fieldsContainer.appendChild(fieldWrap); + return; // already appended + } else if(f.type==='date'){ + input = document.createElement('input'); input.className='input'; input.type='date'; + const curd = (c.options && c.options[f.name]!==undefined) ? c.options[f.name] : (c[f.name]!==undefined?c[f.name]:''); + input.value = curd ? String(curd) : ''; + input.oninput = syncToData; } else { input = document.createElement('input'); input.className='input'; input.type='text'; input.oninput = syncToData; } @@ -141,6 +182,12 @@ function _close_shift_modal_targets(){ v = v===''?null:parseFloat(v); } else if(f.type==='list'){ v = v===''?[]:v.split(',').map(x=>x.trim()).filter(Boolean); + } else if(f.type==='list_int'){ + v = v===''?[]:v.split(',').map(x=>parseInt(x.trim(),10)).filter(n=>!Number.isNaN(n)); + } else if(f.type==='bool'){ + const cb = fieldsContainer.querySelector('[data-field-name="'+f.name+'"]'); if(cb) v = !!cb.checked; else v = !!v; + } else if(f.type==='date'){ + v = v===''?null:v; // keep as ISO date string } else if(f.type==='json'){ try{ v = v===''?{}:JSON.parse(v); }catch(e){ v = input.value; } }