Add shift filtering and rendering functionality in shifts settings
This commit is contained in:
@@ -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 class='shifts-table' style='width:100%; border-collapse:collapse; margin-bottom:8px;'></table>");
|
||||
$table.append(`<tr><th style='text-align:left'>Name</th><th style='text-align:left'>Sites</th><th style='text-align:left'>Days</th><th style='text-align:left'>Balance offset</th></tr>`);
|
||||
if (shiftsJson && Array.isArray(shiftsJson)) {
|
||||
shiftsJson.forEach(s => {
|
||||
const sites = (s.sites || []).join(", ");
|
||||
const days = (s.days || []).join(", ");
|
||||
$table.append(`<tr class='shift-row' data-shift-name='${s.name}'><td>${s.name}</td><td>${sites}</td><td>${days}</td><td>${s.balance_offset ?? ''}</td></tr>`);
|
||||
});
|
||||
} 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('<div style="margin-bottom:8px;"><input type="text" id="worker-filter" placeholder="Filter workers by name/site" style="margin-right:8px;"/> <select id="worker-shift-select"><option value="">(All shifts)</option></select></div>');
|
||||
}
|
||||
const $sel = $("#worker-shift-select");
|
||||
$sel.empty().append('<option value="">(All shifts)</option>');
|
||||
shiftNames.forEach(s => $sel.append(`<option value="${s}">${s}</option>`));
|
||||
|
||||
// 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 = $("<div id='worker-list' style='margin-top:8px;'></div>");
|
||||
$("#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 = $(`<div class='worker-card' style='border:1px solid #ccc;padding:8px;margin-bottom:6px;'><strong>${name}</strong> <span style='color:#666'>${site}</span><div>FTE: ${fte}</div></div>`);
|
||||
// show shift targets summary
|
||||
if (w.shift_target_number) {
|
||||
const tbl = $("<table style='margin-top:6px; width:100%;'></table>");
|
||||
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(`<tr><td style='width:60%'>${s}</td><td style='text-align:right'>${c} assigned</td><td style='text-align:right'>target ${parseFloat(t).toFixed(2)}</td></tr>`);
|
||||
});
|
||||
card.append(tbl);
|
||||
}
|
||||
$list.append(card);
|
||||
});
|
||||
}
|
||||
|
||||
// initialize panels
|
||||
renderShiftSettings();
|
||||
renderWorkerFilterOptions();
|
||||
renderWorkerList();
|
||||
|
||||
// Column for prior-adjustment (hidden by default)
|
||||
h.append(`<th class='prior-adjust-col' style='display:none'>Prior Adjust</th>`);
|
||||
|
||||
|
||||
@@ -5722,9 +5722,14 @@ class RotaBuilder(object):
|
||||
</div>
|
||||
<details>
|
||||
<summary><h2>Shifts settings</h2></summary>
|
||||
<div id="shifts-container" data-shifts='{json.dumps([i.name for i in self.shifts])}'>
|
||||
<div id="shifts-container" data-shifts='{json.dumps([i.name for i in self.shifts])}' data-shifts-json='{json.dumps([_pydantic_to_dict(i) for i in self.shifts])}'>
|
||||
<div id="shifts-controls">
|
||||
<input type="text" id="shift-filter" placeholder="Filter shifts by name" style="width:100%; margin-bottom:8px;" />
|
||||
</div>
|
||||
<div id="shifts-table">
|
||||
{"<br/>".join(str(i) for i in self.shifts)}
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
<details>
|
||||
<summary><h2>Worker details</h2></summary>
|
||||
|
||||
Reference in New Issue
Block a user