Enhance textarea synchronization and improve WorkerForm save logic
This commit is contained in:
@@ -84,6 +84,27 @@ function _close_shift_modal_targets(){
|
||||
data.forEach((c, idx)=>{
|
||||
listEl.appendChild(renderConstraintRow(c, idx));
|
||||
});
|
||||
// Ensure textarea is synchronized with the rendered rows. Some
|
||||
// inputs update the textarea via their handlers; this function
|
||||
// triggers those handlers or falls back to reconstructing the
|
||||
// JSON from DOM if needed.
|
||||
function syncTextarea(){
|
||||
try{
|
||||
const children = listEl.children;
|
||||
for(let i=0;i<children.length;i++){
|
||||
const ch = children[i];
|
||||
// dispatch change/input events on controls so their handlers
|
||||
// update the textarea via syncToData
|
||||
const controls = ch.querySelectorAll('select, input, textarea');
|
||||
controls.forEach(ctrl=>{
|
||||
try{
|
||||
if(ctrl.tagName==='SELECT') ctrl.dispatchEvent(new Event('change', {bubbles:true}));
|
||||
else ctrl.dispatchEvent(new Event('input', {bubbles:true}));
|
||||
}catch(e){}
|
||||
});
|
||||
}
|
||||
}catch(e){ /* ignore */ }
|
||||
}
|
||||
syncTextarea();
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,68 @@
|
||||
function parseInitial(){
|
||||
let val = textarea ? textarea.value : '';
|
||||
if(!val) return [];
|
||||
try{ const p = JSON.parse(val); return Array.isArray(p)?p:[]; }catch(e){ return []; }
|
||||
let raw;
|
||||
try{ raw = JSON.parse(val); }catch(e){ return []; }
|
||||
if(!Array.isArray(raw)) return [];
|
||||
|
||||
// Normalize entries: the stored shape may be a list of ints (0=Mon),
|
||||
// strings, or objects {day: 'Mon', start_date, end_date}.
|
||||
return raw.map(item=>{
|
||||
if(item === null || item === undefined) return {};
|
||||
if(typeof item === 'number'){
|
||||
return { day: DAYS[item] || '' };
|
||||
}
|
||||
if(typeof item === 'string'){
|
||||
// Accept 'monday' / 'Mon' / 'mon' / '0' etc.
|
||||
const n = parseInt(item,10);
|
||||
if(!Number.isNaN(n)) return { day: DAYS[n] || '' };
|
||||
const s = item.trim();
|
||||
// try matching first 3 letters ignoring case
|
||||
const low = s.toLowerCase();
|
||||
for(let d of DAYS){ if(d.toLowerCase().startsWith(low.slice(0,3))) return { day: d }; }
|
||||
// fallback: return as-day string
|
||||
return { day: s };
|
||||
}
|
||||
if(typeof item === 'object'){
|
||||
// If object has numeric day, convert
|
||||
const copy = Object.assign({}, item);
|
||||
if(copy.day !== undefined && typeof copy.day === 'number'){
|
||||
copy.day = DAYS[copy.day] || '';
|
||||
}
|
||||
// If day is a short name, normalise to 3-letter capitalised
|
||||
if(copy.day && typeof copy.day === 'string'){
|
||||
const s = copy.day.trim();
|
||||
const low = s.toLowerCase();
|
||||
for(let d of DAYS){ if(d.toLowerCase().startsWith(low.slice(0,3))) { copy.day = d; break; } }
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
return {};
|
||||
});
|
||||
}
|
||||
|
||||
function syncTextarea(){
|
||||
// Ensure each row's inputs trigger their onchange handlers so the
|
||||
// hidden textarea is kept in sync. This is resilient whether rows
|
||||
// expose a `_sync` helper or rely on input events.
|
||||
try{
|
||||
const children = listEl.children;
|
||||
for(let i=0;i<children.length;i++){
|
||||
const ch = children[i];
|
||||
if(ch && typeof ch._sync === 'function'){
|
||||
try{ ch._sync(); }catch(e){}
|
||||
} else {
|
||||
// trigger change/input events on contained controls
|
||||
const controls = ch.querySelectorAll('select, input, textarea');
|
||||
controls.forEach(ctrl=>{
|
||||
try{
|
||||
if(ctrl.tagName==='SELECT') ctrl.dispatchEvent(new Event('change', {bubbles:true}));
|
||||
else ctrl.dispatchEvent(new Event('input', {bubbles:true}));
|
||||
}catch(e){}
|
||||
});
|
||||
}
|
||||
}
|
||||
}catch(e){ /* ignore */ }
|
||||
}
|
||||
|
||||
function render(){
|
||||
|
||||
+10
-42
@@ -444,57 +444,25 @@ class WorkerSelfServiceForm(forms.ModelForm):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def save(self, commit=True):
|
||||
"""Persist FTE and non-working days (nwds) into the Worker instance.
|
||||
|
||||
The form exposes only `fte` and `nwds`. `fte` is handled by the
|
||||
ModelForm machinery; we need to ensure `nwds` is persisted inside
|
||||
`instance.options['nwds']` as a list of ints.
|
||||
"""
|
||||
instance = super().save(commit=False)
|
||||
# persist non-working days into instance.options['nwds'] as list of ints
|
||||
|
||||
# Persist non-working days into instance.options['nwds'] as list of ints
|
||||
opts = instance.options or {}
|
||||
nwds = self.cleaned_data.get('nwds') or []
|
||||
try:
|
||||
opts['nwds'] = [int(x) for x in nwds]
|
||||
except Exception:
|
||||
# fallback: keep as-is
|
||||
opts['nwds'] = nwds
|
||||
instance.options = opts
|
||||
if commit:
|
||||
instance.save()
|
||||
return instance
|
||||
|
||||
def save(self, commit=True):
|
||||
instance = super().save(commit=False)
|
||||
end_date = self.cleaned_data.get("end_date")
|
||||
if end_date:
|
||||
instance.end_date = end_date
|
||||
# Collect option fields and persist into instance.options
|
||||
opts = instance.options or {}
|
||||
# constraint-based option fields
|
||||
for key in getattr(self, "_constraint_defaults", {}).keys():
|
||||
field_name = f"opt__{key}"
|
||||
if field_name in self.cleaned_data:
|
||||
val = self.cleaned_data[field_name]
|
||||
default = self._constraint_defaults.get(key)
|
||||
# Parse JSON for list/dict fields
|
||||
if isinstance(default, (list, dict)):
|
||||
try:
|
||||
parsed = json.loads(val) if val is not None and val != "" else []
|
||||
except Exception:
|
||||
parsed = val
|
||||
opts[key] = parsed
|
||||
else:
|
||||
opts[key] = val
|
||||
|
||||
# builder args (prefixed with opt__)
|
||||
builder_keys = [
|
||||
"balance_offset_modifier",
|
||||
"ltft_balance_offset",
|
||||
"use_previous_shifts",
|
||||
"use_shift_balance_extra",
|
||||
"use_bank_holiday_extra",
|
||||
"allow_force_assignment_with_leave_conflict",
|
||||
]
|
||||
for k in builder_keys:
|
||||
field_name = f"opt__{k}"
|
||||
if field_name in self.cleaned_data:
|
||||
opts[k] = self.cleaned_data[field_name]
|
||||
|
||||
instance.options = opts
|
||||
|
||||
if commit:
|
||||
instance.save()
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user