diff --git a/output/timetable.js b/output/timetable.js index 9231436..d9bfc20 100644 --- a/output/timetable.js +++ b/output/timetable.js @@ -342,6 +342,127 @@ oshifts.forEach((s) => { }); +// Render formatted Shifts settings and Worker details with filtering +function renderShiftSettings() { + const $container = $("#shifts-container"); + if (!$container.length) return; + let shiftsJson = $container.data("shifts-json"); + try { + if (typeof shiftsJson === 'string') shiftsJson = JSON.parse(shiftsJson); + } catch (e) { + // fallback: no structured data + shiftsJson = null; + } + + const $table = $("
"); + $table.append(`NameSitesDaysBalance offset`); + if (shiftsJson && Array.isArray(shiftsJson)) { + shiftsJson.forEach(s => { + const sites = (s.sites || []).join(", "); + const days = (s.days || []).join(", "); + $table.append(`${s.name}${sites}${days}${s.balance_offset ?? ''}`); + }); + } else { + // fallback to existing content + const html = $("#shifts-table").html(); + $("#shifts-table").empty().append(html); + } + $("#shifts-table").empty().append($table); + + // filter input + $("#shift-filter").on("input", (e) => { + const v = $(e.target).val().toLowerCase(); + $(".shift-row").each((i, el) => { + const name = $(el).data('shift-name') + ''; + $(el).toggle(name.toLowerCase().indexOf(v) !== -1); + }); + // update shift select in worker panel if present + renderWorkerFilterOptions(); + }); +} + +function renderWorkerFilterOptions() { + const $wd = $("#worker_details"); + if (!$wd.length) return; + let workers = $wd.data("workers"); + try { if (typeof workers === 'string') workers = JSON.parse(workers); } catch (e) { workers = null; } + // populate shift select with shifts present in current shifts table + const shiftNames = []; + $(".shift-row:visible").each((i, el) => shiftNames.push($(el).data('shift-name'))); + const $select = $("#worker-shift-select"); + if ($select.length === 0) { + $("#worker_details").prepend('
'); + } + const $sel = $("#worker-shift-select"); + $sel.empty().append(''); + shiftNames.forEach(s => $sel.append(``)); + + // attach events + $("#worker-filter").off('input').on('input', () => renderWorkerList()); + $sel.off('change').on('change', () => renderWorkerList()); +} + +function renderWorkerList() { + const $wd = $("#worker_details"); + if (!$wd.length) return; + let workers = $wd.data("workers"); + try { if (typeof workers === 'string') workers = JSON.parse(workers); } catch (e) { workers = null; } + const filter = ($("#worker-filter").val() || '').toLowerCase(); + const shiftFilter = ($("#worker-shift-select").val() || ''); + + let $list = $("#worker-list"); + if ($list.length === 0) { + $list = $("
"); + $("#worker_details").append($list); + } + $list.empty(); + if (!workers) return; + workers.forEach(w => { + const name = (w.name || '') + ''; + const site = (w.site || '') + ''; + if (filter && name.toLowerCase().indexOf(filter) === -1 && site.toLowerCase().indexOf(filter) === -1) return; + // if shiftFilter, ensure worker has target or assigned for that shift + // attempt to read per-worker targets and counts from DOM if not present in JSON + let targets = w.shift_target_number || {}; + let counts = w.shift_counts || {}; + const domRow = $(`td.worker[data-worker='${name}']`).first(); + if (domRow && domRow.length) { + try { + const domTargets = domRow.data('worker-targets'); + if (domTargets && Object.keys(targets).length === 0) targets = domTargets; + } catch (e) {} + try { + const domCounts = domRow.data('shift-counts'); + if (domCounts && Object.keys(counts).length === 0) counts = domCounts; + } catch (e) {} + } + if (shiftFilter) { + const assigned = (counts && counts[shiftFilter]) ? counts[shiftFilter] : 0; + const hasTarget = Object.keys(targets || {}).indexOf(shiftFilter) !== -1 && (targets[shiftFilter] || 0) > 0; + const hasAssigned = assigned > 0; + if (!hasTarget && !hasAssigned) return; + } + const fte = w.fte || ''; + const card = $(`
${name} ${site}
FTE: ${fte}
`); + // show shift targets summary + if (w.shift_target_number) { + const tbl = $("
"); + Object.keys(w.shift_target_number).forEach(s => { + const t = w.shift_target_number[s]; + const c = (w.shift_counts && w.shift_counts[s]) ? w.shift_counts[s] : 0; + tbl.append(`${s}${c} assignedtarget ${parseFloat(t).toFixed(2)}`); + }); + card.append(tbl); + } + $list.append(card); + }); +} + +// initialize panels +renderShiftSettings(); +renderWorkerFilterOptions(); +renderWorkerList(); + // Column for prior-adjustment (hidden by default) h.append(`Prior Adjust`); diff --git a/rota_generator/shifts.py b/rota_generator/shifts.py index 966fafc..34dcc88 100644 --- a/rota_generator/shifts.py +++ b/rota_generator/shifts.py @@ -5722,9 +5722,14 @@ class RotaBuilder(object):

Shifts settings

-
+
+
+ +
+
{"
".join(str(i) for i in self.shifts)}
+

Worker details