feat(classifier): "Partial" Show filter — assigned in the other tab only

Adds a fourth source-tree bucket relative to the active axis: a file assigned on
the OTHER axis but not this one. With its own "Show Partial" toggle (and count)
you can assign a batch in one tab, switch tabs, show only Partial, and finish
them off — working in chunks across the two axes.

fileCategory now returns excluded / assigned (this axis) / partial (other axis
only) / unassigned (neither); filters, counts and the toolbar checkbox follow.

Test: a tracking-only file reads as Partial on the transmittal tab and hides
when Partial is unchecked (51 green).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
ZDDC 2026-06-11 08:27:39 -05:00
parent 823cfb0d48
commit 645b308ebc
4 changed files with 43 additions and 10 deletions

View file

@ -145,6 +145,7 @@
hideCompliantLabel: document.getElementById('hideCompliantLabel'),
classifyFilters: document.getElementById('classifyFilters'),
showUnassignedCheckbox: document.getElementById('showUnassignedCheckbox'),
showPartialCheckbox: document.getElementById('showPartialCheckbox'),
showAssignedCheckbox: document.getElementById('showAssignedCheckbox'),
showExcludedCheckbox: document.getElementById('showExcludedCheckbox'),
showEmptyCheckbox: document.getElementById('showEmptyCheckbox'),
@ -354,13 +355,14 @@
if (app.modules.tree && app.modules.tree.setShowFilters) {
app.modules.tree.setShowFilters({
unassigned: app.dom.showUnassignedCheckbox.checked,
partial: app.dom.showPartialCheckbox.checked,
assigned: app.dom.showAssignedCheckbox.checked,
excluded: app.dom.showExcludedCheckbox.checked,
empty: app.dom.showEmptyCheckbox.checked,
});
}
}
[app.dom.showUnassignedCheckbox, app.dom.showAssignedCheckbox, app.dom.showExcludedCheckbox, app.dom.showEmptyCheckbox]
[app.dom.showUnassignedCheckbox, app.dom.showPartialCheckbox, app.dom.showAssignedCheckbox, app.dom.showExcludedCheckbox, app.dom.showEmptyCheckbox]
.forEach(function (cb) { if (cb) cb.addEventListener('change', pushClassifyFilters); });
// Collapse tree button

View file

@ -41,29 +41,35 @@
// excluded — and three "Show …" toggles control which buckets are visible
// (so unchecking Assigned+Excluded leaves only what's left to do). A folder
// whose whole scanned subtree is filtered away is itself hidden.
var showFilters = { unassigned: true, assigned: true, excluded: true };
var showFilters = { unassigned: true, partial: true, assigned: true, excluded: true };
var showEmpty = true; // show folders that contain no files
function setShowFilters(f) {
showFilters = {
unassigned: f.unassigned !== false,
partial: f.partial !== false,
assigned: f.assigned !== false,
excluded: f.excluded !== false,
};
showEmpty = f.empty !== false;
render();
}
function allFiltersOn() { return showFilters.unassigned && showFilters.assigned && showFilters.excluded; }
function allFiltersOn() { return showFilters.unassigned && showFilters.partial && showFilters.assigned && showFilters.excluded; }
function activeAxis() {
var tt = window.app.modules.targetTree;
return (tt && tt.activeAxis) ? tt.activeAxis() : 'tracking';
}
// Bucket a file relative to the active axis: 'excluded' | 'assigned' | 'unassigned'.
// Bucket a file relative to the active axis:
// 'excluded' | 'assigned' (on this axis) | 'partial' (assigned on the OTHER
// axis only — the to-do for this tab) | 'unassigned' (neither axis).
function fileCategory(file) {
var c = window.app.modules.classify;
var a = c.getAssignment(c.srcKeyForFile(file));
if (a && a.excluded) return 'excluded';
var assigned = a && (activeAxis() === 'transmittal' ? a.transmittalNodeId : a.trackingNodeId);
return assigned ? 'assigned' : 'unassigned';
var onTransmittal = activeAxis() === 'transmittal';
var here = a && (onTransmittal ? a.transmittalNodeId : a.trackingNodeId);
if (here) return 'assigned';
var other = a && (onTransmittal ? a.trackingNodeId : a.transmittalNodeId);
return other ? 'partial' : 'unassigned';
}
function classifyAllows(file) { return !classifyOn() || !!showFilters[fileCategory(file)]; }
@ -142,9 +148,9 @@
}
function updateFilterCounts() {
if (!classifyOn()) return;
var n = { unassigned: 0, assigned: 0, excluded: 0 };
var n = { unassigned: 0, partial: 0, assigned: 0, excluded: 0 };
allClassifyFiles().forEach(function (f) { n[fileCategory(f)]++; });
['unassigned', 'assigned', 'excluded'].forEach(function (k) {
['unassigned', 'partial', 'assigned', 'excluded'].forEach(function (k) {
var el = document.getElementById('show' + k.charAt(0).toUpperCase() + k.slice(1) + 'Count');
if (el) el.textContent = '(' + n[k] + ')';
});

View file

@ -65,11 +65,15 @@
</div>
<div id="classifyFilters" class="classify-filters tree-toolbar" hidden>
<span class="tree-toolbar__label">Show</span>
<label class="checkbox-label" title="Files not yet assigned in the active tab">
<label class="checkbox-label" title="Not assigned on either axis">
<input type="checkbox" id="showUnassignedCheckbox" checked>
Unassigned <span class="filter-count" id="showUnassignedCount"></span>
</label>
<label class="checkbox-label" title="Files already assigned in the active tab">
<label class="checkbox-label" title="Assigned in the OTHER tab but not this one — the to-do for this tab">
<input type="checkbox" id="showPartialCheckbox" checked>
Partial <span class="filter-count" id="showPartialCount"></span>
</label>
<label class="checkbox-label" title="Already assigned in the active tab">
<input type="checkbox" id="showAssignedCheckbox" checked>
Assigned <span class="filter-count" id="showAssignedCount"></span>
</label>

View file

@ -1007,3 +1007,24 @@ test('revision cell links to preview its file and shows no count bubble', async
expect(r.previewKey).toBe('foundation.pdf');
expect(r.hasBadge).toBe(false); // no count bubble
});
test('Show Partial surfaces files assigned in the other tab only', async ({ page }) => {
await page.click('#modeClassifyBtn');
const r = await page.evaluate(() => {
const c = window.app.modules.classify, tree = window.app.modules.tree, tt = window.app.modules.targetTree;
c.reset();
const f = { originalFilename: 'a', extension: 'pdf', folderPath: 'Docs' };
window.app.folderTree = [{ name: 'Docs', path: 'Docs', expanded: true, scanState: 'done', children: [], files: [f] }];
// Assign tracking only, then view the TRANSMITTAL tab → it reads as "partial" there.
const leaf = c.addTrackingPath(null, c.parseFolderLevels('ACME-MECH-0001_A (IFR)'));
c.place([c.srcKeyForFile(f)], leaf, 'tracking');
tt.showTab('transmittal');
tree.render();
const withPartial = !!document.querySelector('#folderTree .file-item');
tree.setShowFilters({ unassigned: true, partial: false, assigned: true, excluded: true });
const withoutPartial = !!document.querySelector('#folderTree .file-item');
return { withPartial, withoutPartial };
});
expect(r.withPartial).toBe(true); // shown while Partial is on (to-do for this tab)
expect(r.withoutPartial).toBe(false); // hidden once Partial is off
});