|
|
@@ -634,11 +634,39 @@
|
|
|
$root.find('[data-owner-filter-dropdown]').toggleClass('hidden');
|
|
|
});
|
|
|
|
|
|
- // --- Filters (search / prio / owner) ----------------------------------
|
|
|
+ // --- Focus filter (single sprint worker, persisted) -------------------
|
|
|
+
|
|
|
+ const focusKey = 'sp:' + sprintId + ':focusWorker';
|
|
|
+ let focusWorker = (function () {
|
|
|
+ try {
|
|
|
+ const raw = window.localStorage.getItem(focusKey);
|
|
|
+ if (raw === null) { return ''; }
|
|
|
+ return String(raw);
|
|
|
+ } catch (_) { return ''; }
|
|
|
+ })();
|
|
|
+
|
|
|
+ function persistFocus() {
|
|
|
+ try { window.localStorage.setItem(focusKey, String(focusWorker)); }
|
|
|
+ catch (_) { /* ignore */ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function updateFocusUi() {
|
|
|
+ const $sel = $root.find('[data-focus-select]');
|
|
|
+ if ($sel.length === 0) { return; }
|
|
|
+ // If the stored worker is no longer a sprint member, fall back to "".
|
|
|
+ if (focusWorker !== '' && $sel.find('option[value="' + focusWorker + '"]').length === 0) {
|
|
|
+ focusWorker = '';
|
|
|
+ persistFocus();
|
|
|
+ }
|
|
|
+ $sel.val(focusWorker);
|
|
|
+ }
|
|
|
+
|
|
|
+ // --- Filters (search / prio / owner / focus) --------------------------
|
|
|
|
|
|
function applyFilters() {
|
|
|
const q = String($root.find('[data-task-search]').val() || '').trim().toLowerCase();
|
|
|
const prio = String($root.find('[data-prio-filter]').val() || '');
|
|
|
+ const focus = String(focusWorker || '');
|
|
|
let visibleCount = 0;
|
|
|
|
|
|
$taskTbody.children('tr[data-task-row]').each(function () {
|
|
|
@@ -653,6 +681,12 @@
|
|
|
if (q !== '' && !title.includes(q)) { ok = false; }
|
|
|
if (prio !== '' && rowPrio !== prio) { ok = false; }
|
|
|
if (ownerFilterSet.size > 0 && !ownerFilterSet.has(ownerKey)) { ok = false; }
|
|
|
+ if (focus !== '') {
|
|
|
+ // Zero-tolerance matches capacity math: treat 0 / 0.0 / empty
|
|
|
+ // as "no assignment" (Number(v) > 0, not !== 0).
|
|
|
+ const v = Number($row.find('[data-assign][data-sw-id="' + focus + '"]').val());
|
|
|
+ if (!(v > 0)) { ok = false; }
|
|
|
+ }
|
|
|
|
|
|
$row.toggle(ok);
|
|
|
if (ok) { visibleCount++; }
|
|
|
@@ -660,6 +694,37 @@
|
|
|
|
|
|
const totalRows = $taskTbody.children('tr[data-task-row]').length;
|
|
|
$root.find('[data-task-empty-filter]').toggle(totalRows > 0 && visibleCount === 0);
|
|
|
+
|
|
|
+ applyFocusColumnVisibility();
|
|
|
+ updateResetVisibility();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Column auto-hide: when a focus worker is selected, any sw column that
|
|
|
+ // is all-zero for the currently visible rows collapses. We tag cells
|
|
|
+ // with `.focus-auto-hidden` rather than mutating `hiddenCols` so that
|
|
|
+ // clearing Focus restores whatever the user picked in the Columns
|
|
|
+ // dropdown.
|
|
|
+ function applyFocusColumnVisibility() {
|
|
|
+ // Always clear the transient class first — keeps the function
|
|
|
+ // idempotent and correct when focus goes from set → "".
|
|
|
+ $root.find('.focus-auto-hidden').removeClass('focus-auto-hidden');
|
|
|
+
|
|
|
+ if (!focusWorker) { return; }
|
|
|
+
|
|
|
+ // Walk every sw column. If no currently visible row has a > 0
|
|
|
+ // assignment for that sw, hide every `[data-col="sw-{id}"]` cell.
|
|
|
+ $root.find('[data-task-table] thead th[data-sort-col^="sw-"]').each(function () {
|
|
|
+ const col = String($(this).attr('data-sort-col')); // e.g. "sw-42"
|
|
|
+ const swId = col.slice(3);
|
|
|
+ let anyNonZero = false;
|
|
|
+ $taskTbody.children('tr[data-task-row]:visible').each(function () {
|
|
|
+ const v = Number($(this).find('[data-assign][data-sw-id="' + swId + '"]').val());
|
|
|
+ if (v > 0) { anyNonZero = true; return false; /* break */ }
|
|
|
+ });
|
|
|
+ if (!anyNonZero) {
|
|
|
+ $root.find('[data-col="' + col + '"]').addClass('focus-auto-hidden');
|
|
|
+ }
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
let searchDebounce = null;
|
|
|
@@ -668,6 +733,11 @@
|
|
|
searchDebounce = setTimeout(applyFilters, 120);
|
|
|
});
|
|
|
$root.on('change', '[data-prio-filter]', applyFilters);
|
|
|
+ $root.on('change', '[data-focus-select]', function () {
|
|
|
+ focusWorker = String($(this).val() || '');
|
|
|
+ persistFocus();
|
|
|
+ applyFilters();
|
|
|
+ });
|
|
|
|
|
|
// --- Column visibility toggle (persisted in localStorage) --------------
|
|
|
|
|
|
@@ -707,6 +777,39 @@
|
|
|
if ($(this).is(':checked')) { hiddenCols.delete(v); } else { hiddenCols.add(v); }
|
|
|
persistHiddenCols();
|
|
|
applyColumnVisibility();
|
|
|
+ updateResetVisibility();
|
|
|
+ });
|
|
|
+
|
|
|
+ // --- Reset button (clears every filter + column-hide in one click) ----
|
|
|
+
|
|
|
+ function filtersActive() {
|
|
|
+ const q = String($root.find('[data-task-search]').val() || '').trim();
|
|
|
+ const prio = String($root.find('[data-prio-filter]').val() || '');
|
|
|
+ return q !== ''
|
|
|
+ || prio !== ''
|
|
|
+ || ownerFilterSet.size > 0
|
|
|
+ || String(focusWorker || '') !== ''
|
|
|
+ || hiddenCols.size > 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ function updateResetVisibility() {
|
|
|
+ $root.find('[data-reset-filters]').toggleClass('hidden', !filtersActive());
|
|
|
+ }
|
|
|
+
|
|
|
+ $root.on('click', '[data-reset-filters]', function () {
|
|
|
+ $root.find('[data-task-search]').val('');
|
|
|
+ $root.find('[data-prio-filter]').val('');
|
|
|
+ ownerFilterSet.clear();
|
|
|
+ persistOwnerFilter();
|
|
|
+ focusWorker = '';
|
|
|
+ persistFocus();
|
|
|
+ hiddenCols.clear();
|
|
|
+ persistHiddenCols();
|
|
|
+
|
|
|
+ updateOwnerFilterUi();
|
|
|
+ updateFocusUi();
|
|
|
+ applyColumnVisibility();
|
|
|
+ applyFilters();
|
|
|
});
|
|
|
|
|
|
$root.on('click', '[data-columns-trigger]', function (ev) {
|
|
|
@@ -804,7 +907,13 @@
|
|
|
// Restore persisted task-list UI state BEFORE applyFilters so hidden
|
|
|
// columns don't briefly flash in before being toggled off.
|
|
|
updateOwnerFilterUi();
|
|
|
+ updateFocusUi();
|
|
|
applyColumnVisibility();
|
|
|
applyFilters();
|
|
|
+ // Reset button visibility is a function of every filter; applyFilters
|
|
|
+ // already calls updateResetVisibility, but call it once more at boot
|
|
|
+ // in case the tbody is empty (applyFilters short-circuits nothing,
|
|
|
+ // but this is defensive).
|
|
|
+ updateResetVisibility();
|
|
|
|
|
|
})(jQuery);
|