Add collapsible sections to Ivanti Queue page
Group queue items into a hybrid layout: Inventory section (CARD/GRANITE/DECOM) at top, then vendor-grouped sections for FP/Archer items sorted alphabetically. Each section header is clickable to collapse/expand with chevron indicators. - Extract grouping logic into reusable utility (queueGrouping.js) - Add collapse state management (all sections expanded by default) - Preserve cross-section multi-select, floating action bar, ticket badges - Add 5 property-based tests covering grouping correctness, ordering, empty section omission, count accuracy, and selection independence
This commit is contained in:
63
frontend/src/utils/queueGrouping.js
Normal file
63
frontend/src/utils/queueGrouping.js
Normal file
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Queue grouping utility — extracts the hybrid Inventory + vendor grouping logic
|
||||
* from IvantiTodoQueuePage into a testable pure function.
|
||||
*
|
||||
* Spec: .kiro/specs/queue-collapsible-sections
|
||||
* Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7
|
||||
*/
|
||||
|
||||
const INVENTORY_TYPES = new Set(['CARD', 'GRANITE', 'DECOM']);
|
||||
|
||||
/**
|
||||
* Groups visible queue items into the hybrid section layout.
|
||||
*
|
||||
* @param {Array} visibleItems - Queue items with status 'pending'
|
||||
* @returns {Array<{key: string, label: string, type: string, items: Array}>}
|
||||
*
|
||||
* Rules:
|
||||
* - Items with workflow_type CARD, GRANITE, or DECOM → Inventory section
|
||||
* - Items with workflow_type FP or Archer → grouped by vendor field
|
||||
* - Items with null/undefined/empty vendor → placed in "Unknown" vendor section
|
||||
* - Inventory section appears first (if non-empty)
|
||||
* - Vendor sections sorted alphabetically by label
|
||||
* - Sections with zero items are omitted from output
|
||||
*/
|
||||
export function groupQueueItems(visibleItems) {
|
||||
const inventoryItems = [];
|
||||
const vendorMap = new Map();
|
||||
|
||||
for (const item of visibleItems) {
|
||||
if (INVENTORY_TYPES.has(item.workflow_type)) {
|
||||
inventoryItems.push(item);
|
||||
} else {
|
||||
const vendor = item.vendor?.trim() || 'Unknown';
|
||||
if (!vendorMap.has(vendor)) vendorMap.set(vendor, []);
|
||||
vendorMap.get(vendor).push(item);
|
||||
}
|
||||
}
|
||||
|
||||
const sections = [];
|
||||
|
||||
if (inventoryItems.length > 0) {
|
||||
sections.push({
|
||||
key: 'inventory',
|
||||
label: 'Inventory',
|
||||
type: 'inventory',
|
||||
items: inventoryItems,
|
||||
});
|
||||
}
|
||||
|
||||
const sortedVendors = [...vendorMap.entries()]
|
||||
.sort((a, b) => a[0].localeCompare(b[0]));
|
||||
|
||||
for (const [vendor, items] of sortedVendors) {
|
||||
sections.push({
|
||||
key: `vendor:${vendor}`,
|
||||
label: vendor,
|
||||
type: 'vendor',
|
||||
items,
|
||||
});
|
||||
}
|
||||
|
||||
return sections;
|
||||
}
|
||||
Reference in New Issue
Block a user